c++booleanstatic-cast

Is static_cast<bool> guaranteed to send a value to 0 or 1?


I am trying to investigate an issue where a database driver expect a certain uint8_t member in a struct to contain 0 or 1.

In the code, this value is assigned via static_cast<bool>(...). I believe that this is sometimes assigning non-zero values for true instead of literally the value 1.

In C, I know the convention for doing this would be !!x. In C++, does static_cast<bool> offer a similar guarantee, or does this require something like the C trick?


EDIT:

The actual code simplified as much as I think reasonable:

#include <vector>
#include <cstdint>

template<typename T>
struct TVector {
   std::vector<T> data;
   
   void Append(const T& value){
      data.push_back(value);
   }
};

struct source {
    //other members

    // Note that this is from shared memory, 'true' could have extra junk 
    // besides just the literal value '1'
    bool is_final; 

    //other members
};

struct worker {

   void append(const source& source) {
       // At some point is_final wasn't a bool, which is I believe 
       // why this static cast is here.
       data.Append(static_cast<bool>(source.is_final)); 
   }

   TVector<uint8_t> data;
};

Solution

  • According to the section [conv.bool] of the C++ standard, when converting an arithmetic type to a bool, the value zero is converted to the value false, and a non-zero value is converted to the value true.

    According to the section [conv.integral], when converting a bool to another integer type, the value false is converted to 0, and the value true is converted to 1.

    Therefore, the expression

    static_cast<uint8_t>( static_cast<bool>(value) )
    

    is guaranteed to yield a value that is either 0 or 1.

    The outer static_cast is not necessary if you ensure that an implicit conversion to uint8_t occurs, as is the case in your posted example. I do not know whether such an implicit conversion occurs in your actual code.

    So, from the point of view of the standard, using static_cast as described above is safe.

    However, my guess is the reason for your problem is that your program has undefined behavior due to reading an indeterminate (i.e. uninitialized) bool value. According to the section [basic.indet] of the C++ standard, if an indeterminate value is produced by an evaluation, in most situations, the behavior is undefined. This means that all guarantees provided by the standard do not apply.

    I used the following test code on several compilers:

    #include <iostream>
    #include <cstring>
    #include <cstdint>
    
    int main()
    {
        uint8_t u = 23;
        bool b;
    
        // give the bool variable the invalid memory
        // representation of the uint8_t value 23
        std::memcpy( &b, &u, 1 );
    
        // perform several tests
        int result1 = static_cast<bool>(b);
        int result2 = !!b;
        int result3 = b?1:0;
        int result4 = b&1;
    
        // print the results of the tests
        std::cout << "static_cast<bool>(b): " << result1 << '\n';
        std::cout << "!!b: " << result2 << '\n';
        std::cout << "b?1:0: " << result3 << '\n';
        std::cout << "b&1: " << result4 << '\n';
    }
    
    

    The results are the following:

    GCC 15.1 with -O0 (most optimizations disabled):

    static_cast<bool>(b): 23
    !!b: 23
    b?1:0: 1
    b&1: 1
    

    GCC 15.1 with -O3 (full optimizations):

    static_cast<bool>(b): 23
    !!b: 23
    b?1:0: 23
    b&1: 23
    

    Clang 20.1.0 (all optimization levels):

    static_cast<bool>(b): 1
    !!b: 1
    b?1:0: 1
    b&1: 1
    

    Microsoft Visual C++ Compiler (all optimization levels):

    static_cast<bool>(b): 23
    !!b: 1
    b?1:0: 1
    b&1: 1
    

    This means that some compilers are not actually converting the byte value 23 to 1 when using static_cast<bool>, !!, b?1:0 or b&1. The compilers are not required to do this, because they are allowed to assume that the bool variable has a memory representation that corresponds to one of the two possible bool values (which have the same memory representations as the uint8_t values 0 and 1). The compilers are not required to take the possiblity into account that the variable contains uninitialized data, because reading uninitialized data invokes undefined behavior.

    In your case, I guess that the bool variable does consist of uninitialized data, due to the following lines in your posted code:

    // Note that this is from shared memory, 'true' could have extra junk 
    // besides just the literal value '1'
    bool is_final;
    

    In that case, the proper solution to your problem is to not read the value from shared memory as a bool if it can contain a byte value other than 0 or 1. Instead, you should read it as a uint8_t, and then convert this value as desired, for example with static_cast<bool>(b), !!b, b?1:0 or b&1.