Consider a Point type with x, y and z values. If I have a range of Point objects, such as std::vector<Point>, what do I need to add to Point to make it work with the std::ranges::views::elements range adaptor?
The intention is to do something like
std::vector<Point> v{...};
for (auto x : v | std::ranges::views::elements<0>) {
// do something with all `x` values
}
The documentation mentions that std::ranges::views::elements works with "tuple-like" values. I have assumed that it should work similarly to how we can make our type work with structured binding, but I seem to be missing something
I have tried with the following code
class Point {
double x=0;
double y=0;
double z=0;
public:
Point(double x, double y, double z) : x(x), y(y), z(z) {}
template <std::size_t N>
double get() const {
if constexpr(N == 0)
return x;
else if constexpr(N == 1)
return y;
else if constexpr(N == 2)
return z;
}
};
namespace std {
template <>
struct tuple_size<Point> : std::integral_constant<std::size_t, 3> {};
template <std::size_t N>
struct tuple_element<N, Point> {
using type = double;
};
}
This is enough to make structured binding work, but std::ranges::views::elements still does not work. Then I thought that maybe std::ranges::views::elements needed std::get<n>(p) to work and I added a specialization below to the std namespace
template <std::size_t N>
double get(const Point &p) {
if constexpr(N == 0)
return p.get<0>();
else if constexpr(N==1)
return p.get<1>();
else if constexpr(N==2)
return p.get<2>();
}
Now it is possible to use std::get<0>(p) to extract the x value, but again this is not enough for std::ranges::views::elements. What else is necessary to make a range of Point objects work with std::ranges::views::elements?
PS: I know I could just use a views::transform here, but my main intention here it to be generic and to understand how these things are suppose to fit together.generic and to understant how these things are supose to fit together.
Can can I make
std::ranges::views::elementswork with a range of my type
No, you can't†.
The way that elements is specified, in [range.elements.view], it's constrained on:
template<class T, size_t N>
concept has-tuple-element = // exposition only
requires(T t) {
typename tuple_size<T>::type;
requires N < tuple_size_v<T>;
typename tuple_element_t<N, T>;
{ get<N>(t) } -> convertible_to<const tuple_element_t<N, T>&>;
};
but we have to keep in mind the general rule in the library, from [contents]/3 that:
Whenever a name
xdefined in the standard library is mentioned, the namexis assumed to be fully qualified as::std::x, unless explicitly described otherwise. For example, if the Effects: element for library functionFis described as calling library functionG, the function::std::Gis meant.
The get<N>(t) there is not an unqualified call to get, it's a call to ::std::get<N>(t) (there is no "unless explicitly described otherwise").
What that means is that this is testing std::get<0> (for keys), and that's not going to find user-provided structured bindings support (which should either be a member e.get<0>() or an unqualified get<0>(e) in the associated namespace). You can't just add overloads into std to make this work.
So... not currently supported.
†Technically, if you stick your overload of std::get<N>(Point) into namespace std and ensure that it is defined before <ranges> is included, this will Just WorkTM. But this is highly fragile, both because you have to carefully control the include order (which you can't really do) and involves adding overloads into std (which you also shouldn't do either, especially in this case where those overloads wouldn't help structured bindings anyway).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With