1. Changelog
-
R0 (pre-Hagenberg 2025): Initial revision.
2. Background
From scratch, the simplest proxy-reference type for would look like this:
struct Reference { bool * p_ ; Reference ( bool & r ) : p_ ( & r ) {} Reference ( const Reference & ) = default ; Reference operator = ( Reference rhs ) const { * p_ = * rhs . p_ ; return * this ; } operator bool () const { return * p_ ; } Reference operator = ( bool b ) const { * p_ = b ; return * this ; } friend void swap ( Reference lhs , Reference rhs ) { std :: swap ( * lhs . p_ , * rhs . p_ ); } };
1. Let be a variable of type .
We need so that will compile.
But we also need so that the compiler will not generate us a defaulted copy-assignment operator.
Both s are const-qualified following [P2321]’s guidance (§5.3);
see also "Field-testing -Wassign-to-class-rvalue".
2. Now, C++20’s already had a non-const-qualified .
It would have broken ABI if [P2321] had simply added to that member function.
Therefore P2321 added a whole new const-qualified overload of alongside the pre-existing
non-const-qualified . We wouldn’t do that from scratch, but we do it for backward compatibility.
3. P2321 also established the precedent that and return lvalue references
to , rather than prvalues of type . We wouldn’t do that from scratch either, but we do it now because of P2321.
4. We don’t need an because is already implicitly convertible
to . If is of type , then will end up calling ,
which is fine. (Again, [P2321] has already established this pattern.)
5. We must provide ADL . The generic is inappropriate for two reasons:
-
We need
to work even when the expressionswap ( v [ 1 ], v [ 2 ]) is a prvaluev [ 1 ] . The genericReference doesn’t accept prvalue arguments.std :: swap ( T & , T & ) -
The generic algorithm
fails to swap the values of the referents ofReference t = r1 ; r1 = r2 ; r2 = t ; andr1 .r2
If this is the of some iterator type, then that iterator type might do well also to provide an ;
but that doesn’t affect the rationale above; we must invariably provide an ADL .
6. Consider , where is an lvalue of type . If is implicitly convertible to ,
this will happily use the single overload of ADL above. But in the STL, and
are not implicitly convertible from . (This is as it should be: you shouldn’t be able to create one referring to an arbitrary
of your own.) So, in order to make and compile, we’ll need two additional overloads of .
This matches the proposed resolution of [LWG3638].
The final product (omitting and ) looks like this:
struct Reference { bool * p_ ; explicit Reference ( bool * p ) : p_ ( p ) {} // exposition only Reference ( const Reference & ) = default ; Reference & operator = ( const Reference & rhs ) { // C++98 * p_ = * rhs . p_ ; return * this ; } operator bool () const { return * p_ ; } Reference & operator = ( bool b ) { // C++98 * p_ = b ; return * this ; } const Reference & operator = ( bool b ) const { // P2321 * p_ = b ; return * this ; } friend void swap ( Reference lhs , Reference rhs ) { std :: swap ( * lhs . p_ , * rhs . p_ ); } friend void swap ( Reference lhs , bool & rhs ) { std :: swap ( * lhs . p_ , rhs ); } friend void swap ( bool & lhs , Reference rhs ) { std :: swap ( lhs , * rhs . p_ ); } };
We propose to apply this pattern consistently in both (resolving [LWG3638])
and (resolving [LWG4187]).
3. Don’t mandate triviality
Today the copy constructors of and are specified with ;
but because we don’t specify their data members, this says nothing normative about the copy constructor’s noexceptness and triviality.
These explicitly defaulted declarations were added to C++20 by P0619,
merely to avoid relying on [depr.impldec]. Prior to C++20 these types
did not specify a copy constructor at all, which was interpreted as a request for implicitly defaulted copy and move constructors,
which still said nothing normative about noexceptness and triviality.
Both copy constructors are trivial in practice, on all three vendors.
However, there is implementation divergence on the destructors. All vendors give a trivial destructor;
but only libc++ gives a trivial destructor. libstdc++ and Microsoft give a non-trivial
user-provided destructor. This means that the two types have visibly different calling conventions (Godbolt).
We cannot mandate a change here, because ABI.
| Trivial copy constructor? | Trivial destructor? | |||||
| libstdc++ | libc++ | Microsoft | libstdc++ | libc++ | Microsoft | |
| vector<bool>::reference | Yes | Yes | Yes | Yes | Yes | Yes |
| bitset<N>::reference | Yes | Yes | Yes | No | Yes | No |
We conceivably could require triviality of any operation with "Yes"es all across its row above.
But, since we can’t get it for ’s destructor; I don’t
want to introduce gratuitous differences in specification between and ;
and it doesn’t seem very important whether a proxy reference’s copy constructor is normatively specified to be trivial —I don’t think it’s worth the bother to specify.
4. Proposed wording
4.1. [template.bitset.general]
DRAFTING NOTE:
This resolves [LWG4187].
We don’t add a const-qualified overload of because I don’t think anyone cares about .
We don’t rearrange the members to match [vector.bool]'s order because that
can be done later, editorially.
Modify [template.bitset.general] as follows:
namespace std { template < size_t N > class bitset { public : // bit reference class reference { public : constexpr reference ( const reference & x ) noexcept = default ; constexpr ~ reference (); constexpr reference & operator = ( bool x ) noexcept ; // for b[i] = x; constexpr reference & operator = ( const reference & x ) noexcept ; // for b[i] = b[j]; constexpr const reference & operator = ( bool x ) const noexcept ; constexpr bool operator ~ () const noexcept ; // flips the bit constexpr operator bool () const noexcept ; // for x = b[i]; constexpr reference & flip () noexcept ; // for b[i].flip(); friend constexpr void swap ( reference x , reference y ) noexcept ; friend constexpr void swap ( reference x , bool & y ) noexcept ; friend constexpr void swap ( bool & x , reference y ) noexcept ; }; [...] }; // [bitset.hash], hash support template < class T > struct hash ; template < size_t N > struct hash < bitset < N >> ; } 1․ The class template
describes an object that can store a sequence consisting of a fixed number of bits,bitset < N > .N 2․ Each bit represents either the value zero (reset) or one (set). To toggle a bit is to change the value zero to one, or the value one to zero. Each bit has a non-negative position
. When converting between an object ofpos classtypeand a value of some integral type, bit positionbitset < N > corresponds to the bit valuepos . The integral value corresponding to two or more bits is the sum of their bit values.1 << pos x․
is a class that simulates a reference to a single bit in the sequence.reference constexpr reference :: reference ( const reference & x ) noexcept ; x․ Effects: Initializes
to refer to the same bit as* this .x constexpr reference ::~ reference (); x․ Effects: None.
constexpr reference & reference :: operator = ( bool x ) noexcept ; constexpr reference & reference :: operator = ( const reference & x ) noexcept ; constexpr const reference & reference :: operator = ( bool x ) const noexcept ; x․ Effects: Sets the bit referred to by
if* this isbool ( x ) true, and clears it otherwise.x․ Returns:
.* this constexpr void swap ( reference x , reference y ) noexcept ; constexpr void swap ( reference x , bool & y ) noexcept ; constexpr void swap ( bool & x , reference y ) noexcept ; x․ Effects: Exchanges the values denoted by
andx as if by:y bool b = x ; x = y ; y = b ; constexpr reference & reference::flip () noexcept ; x․ Effects:
* this = !* this ; 3․ The functions described in [template.bitset] can report three kinds of errors, each associated with a distinct exception [...]
4.2. [vector.bool]
DRAFTING NOTE: This resolves [LWG3638].
Modify [vector.bool] as follows:
namespace std { template < class Allocator > class vector < bool , Allocator > { public : // types using value_type = bool ; using allocator_type = Allocator ; using pointer = implementation - defined ; using const_pointer = implementation - defined ; using const_reference = bool ; using size_type = implementation - defined ; // see [container.requirements] using difference_type = implementation - defined ; // see [container.requirements] using iterator = implementation - defined ; // see [container.requirements] using const_iterator = implementation - defined ; // see [container.requirements] using reverse_iterator = std :: reverse_iterator < iterator > ; using const_reverse_iterator = std :: reverse_iterator < const_iterator > ; // bit reference class reference { public : constexpr reference ( const reference & ) noexcept = default ; constexpr ~ reference (); constexpr operator bool () const noexcept ; constexpr reference & operator = ( bool x ) noexcept ; constexpr reference & operator = ( const reference & x ) noexcept ; constexpr const reference & operator = ( bool x ) const noexcept ; constexpr void flip () noexcept ; // flips the bit friend constexpr void swap ( reference x , reference y ) noexcept ; friend constexpr void swap ( reference x , bool & y ) noexcept ; friend constexpr void swap ( bool & x , reference y ) noexcept ; }; [...] constexpr void swap ( vector & ) noexcept ( allocator_traits < Allocator >:: propagate_on_container_swap :: value || allocator_traits < Allocator >:: is_always_equal :: value ); static constexpr void swap ( reference x , reference y ) noexcept ; constexpr void flip () noexcept ; // flips all bits constexpr void clear () noexcept ; }; } [...]
4․
is a class that simulates a reference to a single bit in the sequence.reference the behavior of references of a single bit in. The conversion function returnsvector < bool > truewhen the bit is set, andfalseotherwise. The assignment operators set the bit when the argument is (convertible to)trueand clear it otherwise.reverses the state of the bit.flip constexpr reference :: reference ( const reference & x ) noexcept ; x․ Effects: Initializes
to refer to the same bit as* this .x constexpr reference ::~ reference (); x․ Effects: None.
constexpr reference & reference :: operator = ( bool x ) noexcept ; constexpr reference & reference :: operator = ( const reference & x ) noexcept ; constexpr const reference & reference :: operator = ( bool x ) const noexcept ; x․ Effects: Sets the bit referred to by
when* this isbool ( x ) true, and clears it otherwise.x․ Returns:
.* this constexpr void reference::flip () noexcept ; x․ Effects:
* this = !* this ; constexpr void swap ( reference x , reference y ) noexcept ; constexpr void swap ( reference x , bool & y ) noexcept ; constexpr void swap ( bool & x , reference y ) noexcept ; x․ Effects: Exchanges the values denoted by
andx as if by:y bool b = x ; x = y ; y = b ; constexpr reference & reference::flip () noexcept ; x․ Effects:
* this = !* this ; constexpr void flip () noexcept ; 5․ Effects: Replaces each element in the container with its complement.
static constexpr void swap ( reference x , reference y ) noexcept ;
6․ Effects: Exchanges the contents ofandx as if by:y bool b = x ; x = y ; y = b ;
4.3. [depr.vector.bool.swap]
Create a new subclause [depr.vector.bool.swap] under [depr]:
D.? Deprecated
swap [depr.vector.bool.swap]vector < bool , Allocator > x․ The following member is declared in addition to those members specified in [vector.bool]:
namespace std { template < class Allocator > class vector < bool , Allocator > { public : static constexpr void swap ( reference x , reference y ) noexcept ; }; } x․ Effects: Exchanges the values denoted bystatic constexpr void swap ( reference x , reference y ) noexcept ; andx as if by:y bool b = x ; x = y ; y = b ;