Chris Chris - 8 months ago 29
C++ Question

Should a boolean value be truncated to either true or false when assigned?

I've found a difference in the value stored in a bool variable (btw Visual-C++ and clang++), in the case where the stored value is neither true nor false (if it was corrupted somehow), and I'm not sure if it's a Visual-C++ bug or if it's just UB I should ignore.

Take the following sample:

#include <cstdint>
#include <iostream>
#include <string>
int main()
{
bool b{ false };
bool const* const pb = reinterpret_cast<bool const*>(&b);
std::uint8_t * const pi = reinterpret_cast<std::uint8_t*>(&b);

std::cout << "b: " << b << " pb: " << (*pb) << " pi: " << std::to_string(*pi) << std::endl;

*pi = 3; // Simulate a bad cast during boolean creation
bool const b2{ b };
bool const b3{ *pb };

std::cout << "b: " << b << " pb: " << (*pb) << " pi: " << std::to_string(*pi) << std::endl;
std::cout << "b2: " << b2 << " b3: " << b3 << std::endl;

return 0;
}


This is the output of Visual-C++

b: 0 pb: 0 pi: 0
b: 3 pb: 3 pi: 3
b2: 3 b3: 3


and this is the output of clang++

b: 0 pb: 0 pi: 0
b: 1 pb: 1 pi: 3
b2: 1 b3: 1


It looks like there is a limits check in clang++ when constructing a new boolean by value, as well as when it is used with stream operator.

Should I just ignore this, or is it a bug that only Visual-C++ has?
Thanks!

Edit:
For those who did not understand the purpose of the sample, it was just a showcase to "simulate" a memory corruption or a bug in another part of the code that caused a boolean value to be initialized with something else than true or false, whatever the binary representation of a bool.

(I was wondering if I have to protect my code from improper usage somewhere else using an assert for example, but only if this behavior is not UB)

Answer Source

Since I didn't, really, find information on casts from pointer-to-bool (or equivalent) in C++ standard (if usage of those is defined), I were reluctant to post this as an answer. But, on the second thought, I may as well post it - it may get elaborated upon by other people.

First of all, The C++14 standard defines bool as:

[basic.fundamental]

  1. Values of type bool are either true or false. [Note:There are no signed, unsigned, short, or long bool types or values. — end note] Values of type bool participate in integral promotions (4.5)

Since it participates in integral promotions, the following promotion is defined for it:

[conv.prom]

  1. A prvalue of type bool can be converted to a prvalue of type int, with false becoming zero and true becoming one.

And, since you are printing with std::ostream::operator<<, for bool, it is defined as follows:

[ostream.inserters.arithmetic]

  1. The classes num_get<> and num_put<> handle locale-dependent numeric formatting and parsing.

Since it uses num_put<> for actual output, the snippet of it, related to bool output is defined as:

[facet.num.put.virtuals]

  1. If (str.flags() & ios_base::boolalpha) == 0 returns do_put(out, str, fill, (int)val)

Since you don't use boolalpha in the example you show - typical integral promotion rules (described above) should apply.

In addition, I still can't explain why std::to_string(*pi) after *pi = 3 is still prints 3 in both cases, but it may, somehow, be related to:

[expr.reinterpret.cast]

  1. [Note: The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value.— end note]