【问题标题】:Can can I make `std::ranges::views::elements` work with a range of my type我可以让 `std::ranges::views::elements` 在我的类型范围内工作吗
【发布时间】:2021-08-16 21:12:28
【问题描述】:

考虑具有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 提到 std::ranges::views::elements 与“类似元组”的值一起使用。我假设它应该类似于我们如何制作我们的类型work with structured binding,但我似乎遗漏了一些东西

我已尝试使用以下代码

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&lt;n&gt;(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(p) 来提取 x 值,但这对于 std::ranges::views::elements 来说还是不够的。要使一系列Point 对象与std::ranges::views::elements 一起工作,还需要什么?


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

【问题讨论】:

  • 你能提供你得到的错误信息吗?
  • 另一种方法是提供一个成员函数,将Point 转换为包含对其成员godbolt.org/z/Kxnsdn1bM 的引用的tuple

标签: c++ c++20 std-ranges


【解决方案1】:

我可以让std::ranges::views::elements 使用我的类型吗

不,你不能

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

每当提到标准库中定义的名称x 时,除非另有明确说明,否则名称x 被假定为完全限定为::​std​::​x。例如,如果 Effects: 库函数F 的元素被描述为调用库函数G,则表示函数::​std​::​G

get&lt;N&gt;(t) 不是对get 的无限制调用,而是对::std::get&lt;N&gt;(t) 的调用(没有“除非另有明确说明”)。

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

所以...目前不支持。


从技术上讲,如果您将 std::get&lt;N&gt;(Point) 的重载粘贴到命名空间 std 并确保它被定义之前 &lt;ranges&gt; 被包括在内,这将只是工作TM。但这非常脆弱,因为您必须仔细控制包含顺序(您实际上不能这样做)并且涉及将重载添加到 std 中(您也不应该这样做,尤其是在这种情况下那些重载无论如何都不会帮助结构化绑定)。

【讨论】:

  • 我想到了这一点,但忽略它似乎非常有用的功能,我认为它是一个缺陷!我找不到任何关于限制的讨论,这似乎最终可以追溯到the second revision of P0789,可悲的是它完全没有任何解释。
  • @DavisHerring elements 是由 P1035 添加的,但这里的问题是没有类似元组的库协议......所以它只支持库元组。加上 ADL get 只是……一个问题。
  • 我检查了“脆弱的解决方案”,它的工作原理与您提到的完全一样。但实际上它非常脆弱。现在我将只在需要时使用 views::transform,而不是让我的 Point 类型与通用 views::elements 一起使用。
【解决方案2】:

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

您的专业化不起作用,因为它不是专业化:它是一个重载,因此在命名空间std 中是不允许的(实际上在&lt;ranges&gt; 中没有通过名称查找找到)。

【讨论】:

  • 我也尝试过使用非会员get 函数,但没有成功。似乎@Barry 的回答是正确的,并且对get&lt;N&gt;(Point) 的调用符合std::,因此不允许ADL 找到我的非成员get 实现。
猜你喜欢
  • 1970-01-01
  • 2021-10-10
  • 1970-01-01
  • 1970-01-01
  • 2015-10-14
  • 1970-01-01
  • 1970-01-01
  • 2022-11-24
  • 1970-01-01
相关资源
最近更新 更多