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::elements
work 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
x
defined in the standard library is mentioned, the namex
is assumed to be fully qualified as::std::
x, unless explicitly described otherwise. For example, if the Effects: element for library functionF
is described as calling library functionG
, the function::std::G
is 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