0

I would like to unpack the members of a cv::Vec4f into its components.

cv::Vec4f line{0,1,2,3};
// What I currently have to do:
float x1 = line[0];
float y1 = line[1];
float x2 = line[2];
float y2 = line[3];
// What I would like to be able to do:
auto [x1, y1, x2, y2] = line;

When I do that I currently get a compilation error with the following text:

error: type cv::Vec<float, 4> decomposes into 1 element, but 4 names were provided

I think the error is because the binding immediately grabs the single underlying array element of the Mat class, when I want to be grabbing the elements of that.

cv::Vec4f line{0, 1, 2, 3};
// What compiles, but is ugly
auto [x1, y1, x2, y2] = line.val;
// Or
const auto& [underlying_data] = line;
auto [x1, y1, x2, y2] = underlying_data;

This works, but is really ugly and eliminates one of the main cases I wanted to use a structured binding, which is iterating through a std::vector<cv::Vec4f> using a range based for loop for example:

std::vector<cv::Vec2i> vecs{{1, 2}, {3, 4}};
for (const auto& [x, y] : vecs) {
... Do stuff
}

Which obviously does not compile due to the previously mentioned issue.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982

3 Answers3

0

I ended up following the requirements listed on cppreference for what defines a structured binding: https://en.cppreference.com/w/cpp/language/structured_binding

And tried to follow the listed Case-2 of a type being "tuple-like"

So I copied std::array's get<> functions and tried to apply them to cv::Vec however it feels a bit wrong to encroach on the cv namespace and really wrong to encroach on the std namespace.

The following code seems to work for my desired use case, but maybe there's some way to reduce some of this boilerplate and just say regardless of what form you are getting, use the get applied to Vec::val.

namespace cv {

template <typename T, int cn> class Vec;

template <std::size_t I, typename T, int cn>
constexpr T &get(Vec<T, cn> &v) noexcept {
  static_assert(I < cn, "Vec index is within bounds");
  return v[I];
}

template <std::size_t I, typename T, int cn>
constexpr T &&get(Vec<T, cn> &&v) noexcept {
  static_assert(I < cn, "Vec index is within bounds");
  return std::move(v[I]);
}

template <std::size_t I, typename T, int cn>
constexpr const T &get(const Vec<T, cn> &v) noexcept {
  static_assert(I < cn, "Vec index is within bounds");
  return v[I];
}

template <std::size_t I, typename T, int cn>
constexpr const T &&get(const Vec<T, cn> &&v) noexcept {
  static_assert(I < cn, "Vec index is within bounds");
  return std::move(v[I]);
}
} // namespace cv

namespace std {

template <typename T, int cn>
struct tuple_size<cv::Vec<T, cn>> : std::integral_constant<std::size_t, cn> {};

template <std::size_t I, typename T, int cn>
struct tuple_element<I, cv::Vec<T, cn>> {
  using type = T;
};

} // namespace std

Edit: From Cppreference for tuple_element and tuple_size user specialization for program-defined types is specifically allowed to make them tuple-like, so I am no longer concerned about specializing Vec. If you have access to C++20 ranges and aren't comfortable encroaching on the namespaces I recommend the solution by Daniel Langr.

0

The question has a C++17 tag, but, possibly, a C++20 solution might also be interesting for someone. I came up with something as follows:

std::vector<Vec2i> v = {{ 1, 2 }, { 3, 4 }};

auto val = std::views::transform([](const auto& _){
  return std::to_array(_.val); });

for (const auto& [x, y] : v | val)
  std::cout << x << ", " << y << std::endl

Live demo with a custom Vec2i (there is no support for OpenCV on Gotbolt): https://godbolt.org/z/vjbTKh4ao.

It would be better to use std::span instead of std::array as a return type for the lambda; however, std::span is seemingly not supported by structured bindings as for now (Structured binding for fixed-size span).

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • Unfortunately, I am stuck with C++17 for the project this is relevant to, but I might use this once I have a relevant project. – Julian Meyers Apr 27 '23 at 19:09
0

I would try to not specialize standard templates, although your solution is legal. I have a nasty preference for wrap/adapt hacks:

auto constexpr get_data =
     std::views::transform(
          [](auto& x){return x.val;});

std::vector<cv::Vec2i> vecs{{1, 2}, {3, 4}};
for (const auto& [x, y] : vecs | get_data) {
... Do stuff
};

But that only works iff val data member is part of the documented API. Other options would result in more complex code:

auto constexpr tie_channels = [] <V> (V& vec){
     return [&]<std::size_t ...i>
            (std::index_sequence<i...>)
            { return std::tie(v[i]...); }
            (std::make_index_sequence
            <std::remove_cvref_t<V>::channels>{});
};

auto constexpr get_channels = std::views::transform(tie_channels);

Then just:

for (const auto& [x, y] : vec | get_channels) {
... Do stuff
};
auto [x1, y1, x2, y2] = tie_channels(line);
Red.Wave
  • 2,790
  • 11
  • 17