Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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-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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  • 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).

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:

  1. 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?
  2. 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 members begin and end
    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 a begin()
    member fails if begin() 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:

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​::​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.

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 ) { 
  }
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading