3258. Range access and initializer_list

Section: 25.3.2 [range.access.begin], 25.3.3 [range.access.end], 25.3.6 [range.access.rbegin], 25.3.7 [range.access.rend] Status: Resolved Submitter: Casey Carter Opened: 2019-08-15 Last modified: 2021-06-23

Priority: 3

View all issues with Resolved status.

Discussion:

The specification of ranges::begin in 25.3.2 [range.access.begin] includes a "poison pill" overload:

template<class T> void begin(initializer_list<T>&&) = delete;
which exists to create an ambiguity with the non-member initializer_list overload of begin in namespace std (17.10.2 [initializer.list.syn]) when performing unqualified lookup, since specializations of initializer_list should not satisfy forwarding-range (25.4.2 [range.range]). The design intent is that const specializations of initializer_list should also not satisfy forwarding-range, although they are rare enough beasts that they were overlooked when this wording is written.

ranges::end (25.3.3 [range.access.end]) has a similar poison pill for initializer_list, which should be changed consistently.

Notably ranges::rbegin (25.3.6 [range.access.rbegin]) and ranges::rend (25.3.6 [range.access.rbegin]) as currently specified accept rvalue initializer_list arguments; they find the initializer_list overloads of std::rbegin and std::rend (24.7 [iterator.range]) via ADL. While I can't put my finger on anything in particular that's broken by this behavior, it seems wise to make rbegin and rend consistent with begin and end for initializer_list until and unless we discover a reason to do otherwise.

[2019-10 Priority set to 3 after reflector discussion]

[2021-06-23 Resolved by adoption of P2091R0 in Prague. Status changed: New → Resolved.]

Proposed resolution:

This wording is relative to N4830.

  1. Modify 25.3.2 [range.access.begin] as follows:

    -1- The name ranges::begin denotes a customization point object (16.3.3.3.5 [customization.point.object]). The expression ranges::begin(E) for some subexpression E is expression-equivalent to:

    (1.1) — E + 0 if E is an lvalue of array type (6.8.4 [basic.compound]).

    (1.2) — Otherwise, if E is an lvalue, decay-copy(E.begin()) if it is a valid expression and its type I models input_or_output_iterator.

    (1.3) — Otherwise, decay-copy(begin(E)) if it is a valid expression and its type I models input_or_output_iterator with overload resolution performed in a context that includes the declarations:

    template<class T> void begin(T&&) = delete;
    template<class T> void begin(initializer_list<T>&&) = delete;
    
    and does not include a declaration of ranges::begin.

    […]

  2. Modify 25.3.3 [range.access.end] as follows:

    -1- The name ranges::end denotes a customization point object (16.3.3.3.5 [customization.point.object]). The expression ranges::end(E) for some subexpression E is expression-equivalent to:

    (1.1) — E + extent_v<T> if E is an lvalue of array type (6.8.4 [basic.compound]) T.

    (1.2) — Otherwise, if E is an lvalue, decay-copy(E.end()) if it is a valid expression and its type S models sentinel_for<decltype(ranges::begin(E))>

    (1.3) — Otherwise, decay-copy(end(E)) if it is a valid expression and its type S models sentinel_for<decltype(ranges::begin(E))> with overload resolution performed in a context that includes the declarations:

    template<class T> void end(T&&) = delete;
    template<class T> void end(initializer_list<T>&&) = delete;
    
    and does not include a declaration of ranges::end.

    […]

  3. Modify 25.3.6 [range.access.rbegin] as follows:

    -1- The name ranges::rbegin denotes a customization point object (16.3.3.3.5 [customization.point.object]). The expression ranges::rbegin(E) for some subexpression E is expression-equivalent to:

    (1.1) — If E is an lvalue, decay-copy(E.rbegin()) if it is a valid expression and its type I models input_or_output_iterator.

    (1.2) — Otherwise, decay-copy(rbegin(E)) if it is a valid expression and its type I models input_or_output_iterator with overload resolution performed in a context that includes the declarations:

    template<class T> void rbegin(T&&) = delete;
    template<class T> void rbegin(initializer_list<T>) = delete;
    
    and does not include a declaration of ranges::rbegin.

    […]

  4. Modify 25.3.7 [range.access.rend] as follows:

    -1- The name ranges::rend denotes a customization point object (16.3.3.3.5 [customization.point.object]). The expression ranges::rend(E) for some subexpression E is expression-equivalent to:

    (1.1) — If E is an lvalue, decay-copy(E.rend()) if it is a valid expression and its type S models

    sentinel_for<decltype(ranges::rbegin(E))>
    

    (1.2) — Otherwise, decay-copy(rend(E)) if it is a valid expression and its type S models

    sentinel_for<decltype(ranges::rbegin(E))>
    
    with overload resolution performed in a context that includes the declarations:
    template<class T> void rend(T&&) = delete;
    template<class T> void rend(initializer_list<T>) = delete;
    
    and does not include a declaration of ranges::rend.

    […]