Table of Contents

If you have two function overloads foo(): one is taking const std::string& and the other taking bool. Which one of them will be selected when you call foo("hello world"); ?

Let’s see where such a case might bite us and cause troubles?

Intro  

Here’s the example once again

void foo(const std::string& in) { std::cout << in << '\n'; }
void foo(bool in) { std::cout << "bool: " << in << '\n';}

foo("Hello World");

What’s the output?

.
.
.

bool: true

And why is that?

Let’s see the standard:

C++17 Draft: Boolean conversions, conv.bool:

A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true….

In other words, when performing a function overload resolution lookup the compiler doesn’t have a function that exactly matches the input parameter (it’s an array of const char), so it has to perform a conversion.

We have two options: convert to bool or convert to a user defined type (std::string is still a custom type).

Both conversion produce a viable function, but (from cppreference):

A standard conversion sequence is always better than a user-defined conversion sequence

That’s why the bool conversion is selected.

Of course, the conversion of a pointer to bool is not always causing troubles. For example, you can write:

if (myPTR) { }

(assuming myPTR is a pointer)

A Case with std::variant  

Another unwanted scenario that might happen is when you have a std::variant with bools and strings. The same conversion to pointer and to bool might happen.

Have a look:

std::variant<std::string, bool, int> var { 42 };
var = "Hello World";

Initially, the variant will have the active type of int, but then you assign a string literal… so it will convert to bool, not to std::string.

Same thing can happen when you initialise a variant with const char*:

std::variant<std::string, bool, int> var { "Hello World" };

Fortunately, such unwanted conversions it about to be fixed for a variant. You can check GCC trunk (10.0) which implements already this C++17 fix: A sane variant converting constructor - P0608.

The paper adds additional enforcements on the constructor and the assignment operator:

Quoting the part from std::variant

variant<float, long, double> v = 0;

Before the fix, this line won’t compile (we have several narrowing conversions possible), but after the improvement, it will hold long.

Here’s a commit for libstdc++ that implements that change:
Implement sane variant converting constructor (P0608R3)

Summary  

In this short blog post, I wanted to tackle an issue of unwanted conversions that might happen when selection a function overload, or an alternative type in std::variant. Although for custom types like variant the library implementations can fix unwanted conversions it’s still good to keep your types as much “consistent” as possible so that you limit the number of conversions.

You can play with code at @Wandbox and switch between compilers, GCC 9.1 and GCC 10.0 to see the difference

  • string and bool: @code
  • float, long double: @code

Have you encountered some “unwanted” conversions that cause bugs in your code? Please share your stories in comments.