c++opencvc++17structured-bindings

How can I use C++17 structured bindings with OpenCV Vec?


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.


Solution

  • 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.