C++ concepts for exact matching in overload resolution

Resolving overload sets in C++ that differ in accepting bool vs std::string as parameters can cause surprises when we call them with string literals as arguments. We’ll see how we can work around this with concepts.


Consider this overload set, where the first overload accepts an std::string and the second accepts a bool:

void show(std::string_view s) {
  std::cout << "Showing the string: " << s << std::endl;
}

void show(bool b) {
  std::cout << "Showing the bool: " << b << std::endl;
}

int main() {
  show(true);
  show("A lovely string");
}

Both calls select the bool overload and therefore the snippet prints Showing the bool: 1 twice.

Perhaps surprisingly, one might have expected show("A lovely string") to call the std::string_view overload instead, and such surprises often lead bugs.

Essentially, this happens due to a conversion sequence from const char[16] (the underlying type of "A lovely string") to bool, and that’s preferred over the alternative conversion to std::string_view.

The same holds if we replace std::string_view with const std::string&.

There are multiple ways to “fix” this, and each one depends on what we want to achieve, for example:

  • not overloading in the first place
  • adding an overload that accepts a const char*
  • explicitly converting the literal into an std::string_view at call-site
  • use a template with SFINAE

I’d like to present another option that improves upon “use a template with SFINAE” with C++20 concepts.

Concepts for an exact match

With a little help from the standard std::same_as concept (and the constrained auto syntax for terseness), we can restrict the overload that accepts something implicitly convertible to a bool to accept exactly a bool. This disables the unwanted conversion to bool in favour of the conversion to std::string_view:

void show(std::string_view s) {
  std::cout << "Showing the string: " << s << std::endl;
}

void show(std::same_as<bool> auto b) {
  std::cout << "Showing the bool: " << b << std::endl;
}

int main() {
  show(true);
  show("A lovely string");
}

Note that bool b has changed to std::same_as<bool> auto b – maybe a bit more verbose, but it works “as expected”.

To be even more strict, we could also change std::string_view to std::same_as<std::string_view> auto and thus require an explicit conversion at call-site:

void show(std::same_as<std::string_view> auto s) {
  std::cout << "Showing the string: " << s << std::endl;
}

void show(std::same_as<bool> auto b) {
  std::cout << "Showing the bool: " << b << std::endl;
}

int main() {
  show(true);
  show("A lovely string"sv);
}

Note that we had to explicitly convert the literal into an std::string_view – in this case using the sv operator, but std::string_view{"A lovely string"} would work just as fine.

Any attempt to call show("A lovely string") now results in a compile error.

And that’s possibly safer.

Tags: c++
Share: X (Twitter) Facebook LinkedIn