标准库中是否有一个概念可以测试范围 for 循环中的可用性

Is there a concept in the standard library that tests for usability in ranged for loops

有许多不同的方法可以使 type/class 在范围 for 循环中可用。例如在 cppreference:

上给出了概述

range-expression is evaluated to determine the sequence or range to iterate. Each element of the sequence, in turn, is dereferenced and is used to initialize the variable with the type and name given in range-declaration.

begin_expr and end_expr are defined as follows:

  • If range-expression is an expression of array type, then begin_expr is __range and end_expr is (__range + __bound), where __bound is the number of elements in the array (if the array has unknown size or is of an incomplete type, the program is ill-formed)
  • If range-expression is an expression of a class type C that has both a member named begin and a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin() and end_expr is __range.end()
  • Otherwise, begin_expr is begin(__range) and end_expr is end(__range), which are found via argument-dependent lookup (non-ADL lookup is not performed).

如果我想在函数模板中使用范围 for 循环,我想限制类型以在范围 for 循环中使用,以触发编译器错误并显示一条很好的“约束不满足”消息。考虑以下示例:

template<typename T>
requires requires (T arg) {
  // what to write here ??
}
void f(T arg) {
    for ( auto e : arg ) {
    }
}

显然,对于一个通用函数,我希望支持上面列出的所有方式,一个类型可以使用它来使自己的范围兼容。

这让我想到了我的问题:

  1. 有没有比手动将所有不同方式组合成自定义方式更好的方式 概念,我可以使用一些标准库概念吗?在里面 concepts library,没有这回事。而如果真的没有这样的东西, 这是有原因的吗?
  2. 如果没有 library/builtin 的概念,我应该如何实现这样的 事物。真正让我困惑的是如何测试成员 beginend 无论此类成员的类型或可访问性如何(引用的第二个项目符号 列表)。用于测试 begin() 是否存在的 requires 子句 如果 begin() 是私有的,则成员失败,但是范围 for 循环将能够使用 不管那个类型。

备注 我知道以下两个问题:

但他们都没有真正回答我的问题。

看来您需要的是 std::ranges::range,它要求表达式 ranges::begin(t)ranges::end(t) 为 well-formed.

其中ranges::begin定义在[range.access.begin]中:

The name ranges​::​begin denotes a customization point object. Given a subexpression E with type T, let t be an lvalue that denotes the reified object for E. Then:

  • If E is an rvalue and enable_­borrowed_­range<remove_­cv_­t<T>> is false, ranges​::​begin(E) is ill-formed.

  • Otherwise, if T is an array type and remove_­all_­extents_­t<T> is an incomplete type, ranges​::​begin(E) is ill-formed with no diagnostic required.

  • Otherwise, if T is an array type, ranges​::​begin(E) is expression-equivalent to t + 0.

  • Otherwise, if auto(t.begin()) is a valid expression whose type models input_­or_­output_­iterator, ranges​::​begin(E) is expression-equivalent to auto(t.begin()).

  • Otherwise, if T is a class or enumeration type and auto(begin(t)) is a valid expression whose type models input_­or_­output_­iterator with overload resolution performed in a context in which unqualified lookup for begin finds only the declarations

    void begin(auto&) = delete; 
    void begin(const auto&) = delete;
    

    then ranges​::​begin(E) is expression-equivalent to auto(begin(t)) with overload resolution performed in the above context.

  • Otherwise, ranges​::​begin(E) is ill-formed.

也就是说,它不仅会对数组类型进行特定的操作,还会根据表达式的有效性来决定是调用成员函数range.begin()还是自由函数begin(range),这已经涵盖了 so-called range-expression 描述的行为。 ranges::end 遵循类似的规则。所以我认为你可以简单地做

template<std::ranges::range T>
void f(T arg) {
  for (auto&& e : arg) { // guaranteed to work
  }
}

需要注意的是ranges::begin要求返回的类型必须是ranges::begin返回的input_or_output_iterator, and ranges::end also requires that the returned type must model sentinel_for类型的模型,这样T就足以成为rangerange-expression 没有这样的约束,它只检查表达式的有效性,因此可以使用 range-based 的最小类型循环 可能是

struct I {
  int operator*();
  I& operator++();
  bool operator!=(const I&) const;
};

struct R {
  I begin();
  I end();
};

for (auto x : R{}) { } // well-formed

但我认为您对这种情况不感兴趣,因为 I 不足以构成有效的迭代器。