2

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?

GreyCat
  • 16,622
  • 18
  • 74
  • 112
  • 3
    `"foo"` should be `const char*` so you may need another variant for that. It could be the pointer is being treated as "close enough" to a `bool`. Does compiling with all warnings (`-Wall`) reveal anything insightful? – tadman Dec 10 '18 at 15:52
  • You could write a type that is only constructible from `bool`, making sure to not make the constructor `explicit` and taking that instead. Since the language won't perform two implicit conversions to satisfy a function overload, only a true `bool` could all it. Edit : Though you would then have an ambiguity. – François Andrieux Dec 10 '18 at 16:00
  • 1
    Every one of those function calls is an error. The code passes one argument to functions that take two arguments. Yes, people can guess at what you meant, but you should post real code. – Pete Becker Dec 10 '18 at 17:59
  • @PeteBecker Fixed, thanks! – GreyCat Dec 11 '18 at 13:07

2 Answers2

3

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;
}
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
3

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"});
AndyG
  • 39,700
  • 8
  • 109
  • 143