There are a number of different ways, that make a type/class usable in a ranged for loop. An overview is for example given on cppreference:
range-expressionis 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_exprandend_exprare defined as follows:
- If
range-expressionis an expression of array type, thenbegin_expris__range
andend_expris (__range+__bound), where__boundis 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-expressionis an expression of a class typeCthat has both a member
namedbeginand a member namedend(regardless of the type or accessibility of
such member), thenbegin_expris__range.begin()andend_expris__range.end()- Otherwise,
begin_exprisbegin(__range)andend_exprisend(__range), which
are found via argument-dependent lookup (non-ADL lookup is not performed).
If I want to use a ranged for loop say in a function template, I want to constrain the type to be usable in a ranged for loop, to trigger a compiler error with a nice "constraint not satisfied" message. Consider the following example:
template<typename T>
requires requires (T arg) {
// what to write here ??
}
void f(T arg) {
for ( auto e : arg ) {
}
}
Obviously for a generic function I want to support all of the above listed ways, that a type can use to make itself ranged for compatible.
This brings me to my questions:
- Is there a better way than manually combining all the different ways into a custom
concept, is there some standard library concept that I can use for that? In the
concepts library, there is no such thing. And if there is really no such thing,
is there a reason for that? - If there is no library/builtin concept for that, how am I supposed to implement such
thing. What really puzzles me, is how to test for membersbeginandend
regardless of the type or accessibility of such member (second bullet in the qouted
list). A requires clause that tests for example for the existence of abegin()
member fails ifbegin()is private, but the ranged for loop would be able to use
the type regardless of that.
Note
I am aware of the following two questions:
- 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"?
but neither of them really answers my question.
>Solution :
It seems like what you need is std::ranges::range which requires the expressions ranges::begin(t) and ranges::end(t) to be well-formed.
Where ranges::begin is defined in [range.access.begin]:
The name
ranges::begindenotes a customization point object. Given a subexpressionEwith 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
Tis 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
Tis a class or enumeration type andauto(begin(t))is a
valid expression whose type modelsinput_or_output_iteratorwith
overload resolution performed in a context in which unqualified lookup
forbeginfinds 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.
That is to say, it will not only perform specific operations on the array but also decide whether to call member function range.begin() or free function begin(range) according to the validity of the expression, this already covers the behavior described by the so-called range-expression. ranges::end has similar behavior. So I think you can simply do
template<std::ranges::range T>
void f(T arg) {
for ( auto e : arg ) {
}
}