标准库中是否有一个概念可以测试范围 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 ) {
}
}
显然,对于一个通用函数,我希望支持上面列出的所有方式,一个类型可以使用它来使自己的范围兼容。
这让我想到了我的问题:
- 有没有比手动将所有不同方式组合成自定义方式更好的方式
概念,我可以使用一些标准库概念吗?在里面
concepts library,没有这回事。而如果真的没有这样的东西,
这是有原因的吗?
- 如果没有 library/builtin 的概念,我应该如何实现这样的
事物。真正让我困惑的是如何测试成员
begin
和 end
无论此类成员的类型或可访问性如何(引用的第二个项目符号
列表)。用于测试 begin()
是否存在的 requires 子句
如果 begin()
是私有的,则成员失败,但是范围 for 循环将能够使用
不管那个类型。
备注
我知道以下两个问题:
- What concept allows a container to be usable in a range-based for loop?
- How to make my custom type to work with "range-based for loops"?
但他们都没有真正回答我的问题。
看来您需要的是 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
就足以成为range
。 range-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
不足以构成有效的迭代器。
有许多不同的方法可以使 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
andend_expr
are defined as follows:
- If
range-expression
is an expression of array type, thenbegin_expr
is__range
andend_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 typeC
that has both a member namedbegin
and a member namedend
(regardless of the type or accessibility of such member), thenbegin_expr
is__range.begin()
andend_expr
is__range.end()
- Otherwise,
begin_expr
isbegin(__range)
andend_expr
isend(__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 ) {
}
}
显然,对于一个通用函数,我希望支持上面列出的所有方式,一个类型可以使用它来使自己的范围兼容。
这让我想到了我的问题:
- 有没有比手动将所有不同方式组合成自定义方式更好的方式 概念,我可以使用一些标准库概念吗?在里面 concepts library,没有这回事。而如果真的没有这样的东西, 这是有原因的吗?
- 如果没有 library/builtin 的概念,我应该如何实现这样的
事物。真正让我困惑的是如何测试成员
begin
和end
无论此类成员的类型或可访问性如何(引用的第二个项目符号 列表)。用于测试begin()
是否存在的 requires 子句 如果begin()
是私有的,则成员失败,但是范围 for 循环将能够使用 不管那个类型。
备注 我知道以下两个问题:
- What concept allows a container to be usable in a range-based for loop?
- How to make my custom type to work with "range-based for loops"?
但他们都没有真正回答我的问题。
看来您需要的是 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 subexpressionE
with typeT
, let t be an lvalue that denotes the reified object forE
. Then:
If
E
is an rvalue andenable_borrowed_range<remove_cv_t<T>>
isfalse
,ranges::begin(E)
is ill-formed.Otherwise, if
T
is an array type andremove_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 tot + 0
.Otherwise, if
auto(t.begin())
is a valid expression whose type modelsinput_or_output_iterator
,ranges::begin(E)
is expression-equivalent toauto(t.begin())
.Otherwise, if
T
is a class or enumeration type andauto(begin(t))
is a valid expression whose type modelsinput_or_output_iterator
with overload resolution performed in a context in which unqualified lookup forbegin
finds only the declarationsvoid begin(auto&) = delete; void begin(const auto&) = delete;
then
ranges::begin(E)
is expression-equivalent toauto(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
就足以成为range
。 range-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
不足以构成有效的迭代器。