1. Changelog
-
R0 (during Hagenberg): Initial (and only) revision. LEWG forwarded to LWG by a vote of 16–11–0–1–0. These changes will be incorporated into P3016R6, to be seen by LWG.
2. Background
In C++23, [iterator.range] defines ten overload sets: , , , , , , , , , , , .
Each of these consists of a generic (template) overload plus dedicated overloads for certain types
(namely, built-in array and ).
[P3016R5] removes the dedicated overloads for wherever possible. For example:
template < class C > constexpr auto data ( C & c ) -> decltype ( c . data ()); template < class C > constexpr auto data ( const C & c ) -> decltype ( c . data ()); template < class T , size_t N > constexpr T * data ( T ( & array )[ N ]) noexcept ; template < class E > constexpr const E * data ( initializer_list < E > il ) noexcept ;
That design was approved for C++26 by LEWG in Tokyo (2024-03-22), 4–8–2–0–0, and proceeded to LWG. However, some in LWG objected that this changed the observable behavior of:
bool f ( std :: initializer_list < int > il ) { return noexcept ( std :: data ( il )); }
So LEWG in Hagenberg (2025-02-10) re-voted, 2–12–5–0–2, to apply conditional noexcept-specs
to the generic overloads of , , , and and re-forward P3016 with
that amendment.
P3016’s motivation was to increase consistency, not decrease it. Therefore, we should
add conditional noexcept-specs not just to but also to ; not just to but also to ; and so on. However, LEWGchair asks that this addition be presented
in a separate paper so that it can be properly discussed. This (P3623) is that paper.
3. Proposal
In the paper standard for C++23, we find ([iterator.range], [valarray.syn], [initializer.list.syn], [fs.filesystem.syn]):
-
,begin : noexcept for arrays,end , andinitializer_list ; otherwise never-noexcept[ recursive_ ] directory_iterator -
,data : noexcept for arrays andempty ; otherwise never-noexceptinitializer_list -
,cbegin : conditionally noexceptcend -
,rbegin ,rend ,crbegin : never-noexceptcrend -
,size : noexcept for arrays; otherwise never-noexceptssize
This is explained by two historical quirks:
-
The generic
andcbegin are conditionally noexcept, despite no other generic algorithm being so. (This is because there are no dedicated overloads ofcend orcbegin , because [LWG2128] didn’t think we needed them. Then, when [LWG2280] wanted to make the array andcend overloadsinitializer_list , there were no dedicated overloads to modify, so we just added a conditional noexcept-spec to the generic template.)noexcept -
The dedicated overloads of
andrbegin are never-noexcept, despite every other dedicated overload being so. (I suspect this is becauserend ’s constructor remains never-noexcept. [N3263] guaranteed thatreverse_iterator would be noexcept for all STL containers, but made no such guarantee about reversing an arbitrary iterator.)ctr . rbegin ()
We propose to add -style conditional noexcept-specs everywhere we can.
3.1. What about rbegin ( arr ) ?
What about and on a built-in array type? The paper standard says that is non-noexcept even on arrays and s. This is [LWG3537].
template < class C > constexpr auto rbegin ( C & c ) -> decltype ( c . rbegin ()); template < class C > constexpr auto rbegin ( const C & c ) -> decltype ( c . rbegin ()); template < class T , size_t N > constexpr reverse_iterator < T *> rbegin ( T ( & array )[ N ]) template < class E > constexpr reverse_iterator < const E *> rbegin ( initializer_list < E > il );
Microsoft STL has put unconditional on and since August 2022.
libstdc++ has put unconditional on and since March 2021.
libc++ continues to leave them both unmarked.
3.2. Implementation experience
Yes: fully on Microsoft, "mostly" on libstdc++, "partly" on libc++.
Microsoft STL has put noexcept-specs on , since pre-2019;
on , , , , , , , , , since August 2022.
GNU libstdc++ has put noexcept-specs on , since January 2015;
on , , since November 2017;
on , , since November 2024.
It continues to leave unmarked the generic and .
libc++ has put noexcept-specs on , , since November 2017;
on since February 2019;
on , since October 2023.
It continues to leave unmarked the generic , , , , , and .
3.3. Tony Table
|
|
4. Proposed wording (relative to the draft IS)
This paper’s proposed wording is expressed as a diff against the current draft standard, for ease of readability.
If P3623 is approved by LEWG, then I’ll create a P3016R6 containing the merge of these changes and [P3016R5]’s changes, and ask LEWG to re-vote to forward P3016R6 to LWG.
If P3623 is rejected, I’ll just plan to revisit this whole area in some later cycle.
4.1. [iterator.synopsis]
Modify [iterator.synopsis] as follows:
25.2 Header <iterator> synopsis [iterator.synopsis]#include <compare>// see [compare.syn] #include <concepts>// see [concepts.syn] namespace std { [...]
// [iterator.range], range access template < class C > constexpr auto begin ( C & c ) noexcept ( noexcept ( c . begin ())) -> decltype ( c . begin ()); template < class C > constexpr auto begin ( const C & c ) noexcept ( noexcept ( c . begin ())) -> decltype ( c . begin ()); template < class C > constexpr auto end ( C & c ) noexcept ( noexcept ( c . end ())) -> decltype ( c . end ()); template < class C > constexpr auto end ( const C & c ) noexcept ( noexcept ( c . end ())) -> decltype ( c . end ()); template < class T , size_t N > constexpr T * begin ( T ( & array )[ N ]) noexcept ; template < class T , size_t N > constexpr T * end ( T ( & array )[ N ]) noexcept ; template < class C > constexpr auto cbegin ( const C & c ) noexcept ( noexcept ( std :: begin ( c ))) -> decltype ( std :: begin ( c )); template < class C > constexpr auto cend ( const C & c ) noexcept ( noexcept ( std :: end ( c ))) -> decltype ( std :: end ( c )); template < class C > constexpr auto rbegin ( C & c ) noexcept ( noexcept ( c . rbegin ())) -> decltype ( c . rbegin ()); template < class C > constexpr auto rbegin ( const C & c ) noexcept ( noexcept ( c . rbegin ())) -> decltype ( c . rbegin ()); template < class C > constexpr auto rend ( C & c ) noexcept ( noexcept ( c . rend ())) -> decltype ( c . rend ()); template < class C > constexpr auto rend ( const C & c ) noexcept ( noexcept ( c . rend ())) -> decltype ( c . rend ()); template < class T , size_t N > constexpr reverse_iterator < T *> rbegin ( T ( & array )[ N ]) noexcept ; template < class T , size_t N > constexpr reverse_iterator < T *> rend ( T ( & array )[ N ]) noexcept ; template < class E > constexpr reverse_iterator < const E *> rbegin ( initializer_list < E > il ) noexcept ; template < class E > constexpr reverse_iterator < const E *> rend ( initializer_list < E > il ) noexcept ; template < class C > constexpr auto crbegin ( const C & c ) noexcept ( noexcept ( std :: rbegin ( c ))) -> decltype ( std :: rbegin ( c )); template < class C > constexpr auto crend ( const C & c ) noexcept ( noexcept ( std :: rend ( c ))) -> decltype ( std :: rend ( c )); template < class C > constexpr auto size ( const C & c ) noexcept ( noexcept ( c . size ())) -> decltype ( c . size ()); template < class T , size_t N > constexpr size_t size ( const T ( & array )[ N ]) noexcept ; template < class C > constexpr auto ssize ( const C & c ) noexcept ( noexcept ( c . size ())) -> common_type_t < ptrdiff_t , make_signed_t < decltype ( c . size ()) >> ; template < class T , ptrdiff_t N > constexpr ptrdiff_t ssize ( const T ( & array )[ N ]) noexcept ; template < class C > constexpr auto empty ( const C & c ) noexcept ( noexcept ( c . empty ())) -> decltype ( c . empty ()); template < class T , size_t N > constexpr bool empty ( const T ( & array )[ N ]) noexcept ; template < class E > constexpr bool empty ( initializer_list < E > il ) noexcept ; template < class C > constexpr auto data ( C & c ) noexcept ( noexcept ( c . data ())) -> decltype ( c . data ()); template < class C > constexpr auto data ( const C & c ) noexcept ( noexcept ( c . data ())) -> decltype ( c . data ()); template < class T , size_t N > constexpr T * data ( T ( & array )[ N ]) noexcept ; template < class E > constexpr const E * data ( initializer_list < E > il ) noexcept ; }
4.2. [iterator.range]
Modify [iterator.range] as follows:
1. In addition to being available via inclusion of the
header, the function templates in [iterator.range] are available when any of the following headers are included:< iterator > ,< array > ,< deque > ,< flat_map > ,< flat_set > ,< forward_list > ,< inplace_vector > ,< list > ,< map > ,< regex > ,< set > ,< span > ,< string > ,< string_view > ,< unordered_map > , and< unordered_set > .< vector > template < class C > constexpr auto begin ( C & c ) noexcept ( noexcept ( c . begin ())) -> decltype ( c . begin ()); template < class C > constexpr auto begin ( const C & c ) noexcept ( noexcept ( c . begin ())) -> decltype ( c . begin ()); 2. Returns:
.c . begin () template < class C > constexpr auto end ( C & c ) noexcept ( noexcept ( c . end ())) -> decltype ( c . end ()); template < class C > constexpr auto end ( const C & c ) noexcept ( noexcept ( c . end ())) -> decltype ( c . end ()); 3. Returns:
.c . end () template < class T , size_t N > constexpr T * begin ( T ( & array )[ N ]) noexcept ; 4. Returns:
.array template < class T , size_t N > constexpr T * end ( T ( & array )[ N ]) noexcept ; 5. Returns:
.array + N template < class C > constexpr auto cbegin ( const C & c ) noexcept ( noexcept ( std :: begin ( c ))) -> decltype ( std :: begin ( c )); 6. Returns:
.std :: begin ( c ) template * lt ; class C > constexpr auto cend ( const C & c ) noexcept ( noexcept ( std :: end ( c ))) -> decltype ( std :: end ( c )); 7. Returns:
.std :: end ( c ) template < class C > constexpr auto rbegin ( C & c ) noexcept ( noexcept ( c . rbegin ())) -> decltype ( c . rbegin ()); template < class C > constexpr auto rbegin ( const C & c ) noexcept ( noexcept ( c . rbegin ())) -> decltype ( c . rbegin ()); 8. Returns:
.c . rbegin () template < class C > constexpr auto rend ( C & c ) noexcept ( noexcept ( c . rend ())) -> decltype ( c . rend ()); template < class C > constexpr auto rend ( const C & c ) noexcept ( noexcept ( c . rend ())) -> decltype ( c . rend ()); 9. Returns:
.c . rend () template < class T , size_t N > constexpr reverse_iterator < T *> rbegin ( T ( & array )[ N ]) noexcept ; 10. Returns:
.reverse_iterator < T *> ( array + N ) template < class T , size_t N > constexpr reverse_iterator < T *> rend ( T ( & array )[ N ]) noexcept ; 11. Returns:
.reverse_iterator < T *> ( array ) template < class E > constexpr reverse_iterator < const E *> rbegin ( initializer_list < E > il ) noexcept ; 12. Returns:
.reverse_iterator < const E *> ( il . end ()) template < class E > constexpr reverse_iterator < const E *> rend ( initializer_list < E > il ) noexcept ; 13. Returns:
.reverse_iterator < const E *> ( il . begin ()) template < class C > constexpr auto crbegin ( const C & c ) noexcept ( noexcept ( std :: rbegin ( c ))) -> decltype ( std :: rbegin ( c )); 14. Returns:
.std :: rbegin ( c ) template < class C > constexpr auto crend ( const C & c ) noexcept ( noexcept ( std :: rend ( c ))) -> decltype ( std :: rend ( c )); 15. Returns:
.std :: rend ( c ) template < class C > constexpr auto size ( const C & c ) noexcept ( noexcept ( c . size ())) -> decltype ( c . size ()); 16. Returns:
.c . size () template < class T , size_t N > constexpr size_t size ( const T ( & array )[ N ]) noexcept ; 17. Returns:
.N template < class C > constexpr auto ssize ( const C & c ) noexcept ( noexcept ( c . size ())) -> common_type_t < ptrdiff_t , make_signed_t < decltype ( c . size ()) >> ; 18. Effects: Equivalent to:
return static_cast < common_type_t < ptrdiff_t , make_signed_t < decltype ( c . size ()) >>> ( c . size ()); template < class T , ptrdiff_t N > constexpr ptrdiff_t ssize ( const T ( & array )[ N ]) noexcept ; 19. Returns:
.N template < class C > constexpr auto empty ( const C & c ) noexcept ( noexcept ( c . empty ())) -> decltype ( c . empty ()); 20. Returns:
.c . empty () template < class T , size_t N > constexpr bool empty ( const T ( & array )[ N ]) noexcept ; 21. Returns:
false.template < class E > constexpr bool empty ( initializer_list < E > il ) noexcept ; 22. Returns:
.il . size () == 0 template < class C > constexpr auto data ( C & c ) noexcept ( noexcept ( c . data ())) -> decltype ( c . data ()); template < class C > constexpr auto data ( const C & c ) noexcept ( noexcept ( c . data ())) -> decltype ( c . data ()); 23. Returns:
.c . data () template < class T , size_t N > constexpr T * data ( T ( & array )[ N ]) noexcept ; 24. Returns:
.array template < class E > constexpr const E * data ( initializer_list < E > il ) noexcept ; 25․ Returns:
.il . begin ()