Standard and User-Defined Conversions

~ 3 minute read.

I just came across a sur­pris­ing bug in my code which I did not ex­pect to cause any prob­lem at all. The sim­pli­fied code goes like this:

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

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

// ...

foo("0");

What do you ex­pect? Dumb ques­tion, sure, I wouldn’t be men­tion­ing this if it would sim­ply print string!.

It in­deed prints bool! be­cause the lit­er­al "0" aka const char[2] is con­vert­ed to const char* and then bool us­ing stan­dard con­ver­sions 1 and is there­fore pri­or­ized over the us­er-de­fined con­ver­sion 2 from const char[2] via std::string(const char*).

So­lu­tion ac­cord­ing to an an­swer on stack­over­flow is us­ing 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 stack­over­flow an­swer is de­cribed as “el­e­gant” work­around. As this re­quires C++14 fea­tures as I lat­er found out, here’s C++11 com­pat­i­ble 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 fi­nal­ly, thank you Vladimír Von­druš for point­ing out an–in my opin­ion–even more el­e­gant so­lu­tion:

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

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

The above will now use stan­dard con­ver­sions and there­fore be cor­rect­ly chose over the bool one. You may want to still pro­vide a const std::string& over­load in your use case.

1
Stan­dard Con­ver­sions
2
Us­er-De­fined Type Con­ver­sions

Writ­ten in 20 min­utes, ex­tend­ed in 10 min­utes.