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 ()