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 of
pos classtypeand a value of some integral type, bit position
bitset < N > corresponds to the bit value
pos . 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 is
bool ( 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
and
x 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 returns
vector < bool > true
when the bit is set, andfalse
otherwise. The assignment operators set the bit when the argument is (convertible to)true
and 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 is
bool ( 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
and
x 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 ofand
x 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 ; and
x as if by:
y bool b = x ; x = y ; y = b ;