我可以使 `std::ranges::views::elements` 与我的类型范围一起工作吗

Can can I make `std::ranges::views::elements` work with a range of my type

考虑具有 xyz 值的 Point 类型。如果我有一系列 Point 对象,例如 std::vector<Point>,我需要向 Point 添加什么才能使其与 std::ranges::views::elements 范围适配器一起使用?

目的是做类似的事情

std::vector<Point> v{...};
for (auto x : v | std::ranges::views::elements<0>) {
    // do something with all `x` values
}

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 ,不过我好像少了点什么

我试过下面的代码

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;
};
}

这足以使结构化绑定起作用,但std::ranges::views::elements仍然不起作用。然后我想也许 std::ranges::views::elements 需要 std::get<n>(p) 才能工作,我在 std 命名空间

下面添加了一个专业化
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>();
}

现在可以使用 std::get<0>(p) 来提取 x 值,但是对于 std::ranges::views::elements 这还是不够的。要使一系列 Point 对象与 std::ranges::views::elements 一起工作还需要什么?


PS:我知道我可以在这里使用 views::transform,但我在这里的主要目的是通用并了解这些东西应该如何适合 together.generic 和了解这些东西应该如何组合在一起。

目的是在您的 命名空间(用于 ADL)中提供非成员 get 函数模板。这 比结构化绑定更严格,结构化绑定也支持成员 get

您的专业化无效,因为它不是专业化:它是一个 重载 ,因此在命名空间 std 中是不允许的(实际上没有被找到从 <ranges>).

中查找名称

Can can I make std::ranges::views::elements work with a range of my type

不,你不能.

elements的指定方式,在[range.elements.view]中,限制在:

  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>&>;
    };

但我们必须牢记库中的一般规则,来自 [contents]/3 的是:

Whenever a name x defined in the standard library is mentioned, the name x is assumed to be fully qualified as​ ::​std​::​x, unless explicitly described otherwise. For example, if the Effects: element for library function F is described as calling library function G, the function​ ::​std​::​G is meant.

get<N>(t) 没有对 get 的不合格调用,它是对 ::std::get<N>(t) 的调用(没有“除非另有明确说明”)。

这意味着这是在测试 std::get<0>(对于 keys),并且不会找到用户提供的结构化绑定支持(应该是成员 e.get<0>() 或关联命名空间中的非限定 get<0>(e))。您不能只在 std 中添加重载来完成这项工作。

所以...目前不支持。


从技术上讲,如果您将 std::get<N>(Point) 的重载粘贴到命名空间 std 中并确保它在 之前被定义 <ranges> 包括在内,这将 Just WorkTM。但这是非常脆弱的,因为你必须仔细控制包含顺序(你不能真正做到)并且涉及向 std 添加重载(你也不应该这样做,特别是在这种情况下无论如何,这些重载对结构化绑定没有帮助。