Standard and User-Defined Conversions

3 minute read.

I just came across a surprising bug in my code which I did not expect to cause any problem at all. The simplified code goes like this:

void foo(const std::string& str) { printf("string!"); }

void foo(const bool b) { printf("bool!"); }

// ...

foo("0");

What do you expect? Dumb question, sure, I wouldn’t be mentioning this if it would simply print string!.

It indeed prints bool! because the literal "0" aka const char[2] is converted to const char* and then bool using standard conversions 1 and is therefore priorized over the user-defined conversion 2 from const char[2] via std::string(const char*).

Solution according to an answer on stackoverflow is using type_traits:

#include <type_traits>

template<typename T, typename S=std::enable_if_t<std::is_same<T, bool>{}>>
void foo(const T b) {
   printf("bool!");
}

Which in that stackoverflow answer is decribed as “elegant” workaround. As this requires C++14 features as I later found out, here’s C++11 compatible code:

#include <type_traits>

template<typename T, typename S=std::enable_if_t<std::is_same<T, bool>::value>::type>
void foo(const T b) {
   printf("bool!");
}

And finally, thank you Vladimír Vondruš for pointing out an—in my opinion—even more elegant solution:

void foo(const char* str) { printf("string!"); }

void foo(const bool b) { printf("bool!"); }

The above will now use standard conversions and therefore be correctly chose over the bool one. You may want to still provide a const std::string& overload in your use case.

Written in 20 minutes, extended in 10 minutes.

Footnotes

  1. Standard Conversions

  2. User-Defined Type Conversions