C++: avoid automatic conversion of string to bool

2019-07-20 14:28发布

问题:

I want to create a set of methods that will output a value with special formatting based on its type. When I'm doing it this way, it looks so far so good:

static void printValue(std::ostringstream& out, int value) {
    out << value;
}

static void printValue(std::ostringstream& out, double value) {
    out << value;
}

static void printValue(std::ostringstream& out, const std::string& value) {
    out << "\"" << escapeString(value) << "\"";
}

Testing:

printValue(std::cout, 123);    // => 123
printValue(std::cout, 3.14);   // => 3.14
printValue(std::cout, "foo");  // => "foo"

However, as soon as I add bool overload:

static void printValue(std::ostringstream& out, bool value) {
    out << (value ? "true" : "false");
}

... things break, as bool-based overload seems to be used by default by add string invocations:

printValue(std::cout, 123);    // => 123
printValue(std::cout, 3.14);   // => 3.14
printValue(std::cout, true);   // => true
printValue(std::cout, "foo");  // => true <= !!!

Is there any way I can escape this automatic cast-to-bool and force compiler to choose a correct method for strings?

回答1:

You could add an template overload that takes a reference to array of char of size N where N is a template parameter, and/or one that accepts a const char*.

template <std::size_t N>
static void printValue(sts::ostringstream& out, const char (&str)[N])
{
    out << str;
}


回答2:

Why this happens:

When the compiler is trying to choose the "best" function to call given some arguments, it prioritizes the overload set in roughly this way:

  1. Exact match: For example, if you pass a const char* and the function accepts a const char*, that's great
  2. Standard conversion sequences: For example, converting a const char[4] to a const char* and then a const char* to bool (based on a check for nullptr). Or promoting a short to an int
    • These conversions also have their own prioritization that we can ignore for the sake of brevity
  3. User defined conversions: basically, conversion to some non-fundamental type (via constructor or conversion operator)

Because you are providing a character literal "foo" it has a standard conversion sequence from const char[4] to const char* (via array to pointer conversion, see [conv.array]) and then from const char* to bool via boolean conversion (see [conv.bool]).

So, while it is possible to construct a const std::string& with "foo" via a user defined conversion, such a conversion is of lower precedence than the standard conversion sequence, and thus the boolean is chosen.

What you can do:

  • Anticipate this and write an overload for const char* or const char[N] like @juanchopanza said
  • create a std::string when you call the function: printValue(std::string{"foo"});