c++c++17bit-fieldsstd-tie

Using std::tie with bit fields seems to fail


I have the following code in C++17 in which I am defining a struct which is a bit mask and has member variables which are bit fields of type bool.

I am defining a tie function so that I can convert it to a comparable std::tuple object, which can be handy. Problem is: std::tie seems to be doing something wrong, and the value of first member variable seems to be false even if the default constructor has correctly initialized it to true.

Manually taking single references of bool to the bit fields seems to work as expected.

I am currently using CLang 12.

This is the code:

#include <cassert>
#include <tuple>

struct Bitmask
{
    Bitmask( )
        : first{true}
        , second{false}
        , third{false}
    {
    }

    bool first : 1;
    bool second : 1;
    bool third : 1;
};

auto
tie( const Bitmask& self )
{
    return std::tie( self.first, self.second, self.third );
}

int main()
{
    Bitmask default_bitmask; 
    Bitmask custom_bitmask;
    custom_bitmask.first = false;
    custom_bitmask.second = true;

    const bool& ref_1 = default_bitmask.first;
    const bool& ref_2 = custom_bitmask.first;

    assert( ref_1 != ref_2 ); // this check works   

    assert( std::get< 0 >( tie( default_bitmask ) ) == true ); // this check fails
    assert( std::get< 0 >( tie( custom_bitmask ) ) == false ); // this check works
}

Solution

  • It is not possible to have a reference or a pointer to a bit field. From cppreference :

    Because bit fields do not necessarily begin at the beginning of a byte, address of a bit field cannot be taken. Pointers and non-const references to bit fields are not possible. When initializing a const reference from a bit field, a temporary is created (its type is the type of the bit field), copy initialized with the value of the bit field, and the reference is bound to that temporary.

    And from the standard :

    The address-of operator & shall not be applied to a bit-field, so there are no pointers to bit-fields. A non-const reference shall not be bound to a bit-field (dcl.init.ref).

    And the accompanying note :

    [Note 3: If the initializer for a reference of type const T& is an lvalue that refers to a bit-field, the reference is bound to a temporary initialized to hold the value of the bit-field; the reference is not bound to the bit-field directly. See dcl.init.ref. — end note]

    The only reason this compiles is because tie takes the Bitmask type as const. Each of its bitfield members are treated as const and std::tie returns a std::tuple of const references. As the passage quoted above says, these references refer to copies of the data members, not those actual data members. Then, just like if you tried to return a const int & x = 10; by reference, you end up with a tuple of dangling references. Finally, trying to get the value of the referenced objects leads to Undefined Behavior.