Resolving overload sets in C++ that differ in accepting
bool
vsstd::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
withconst 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.