Reputation: 339
Getting a C2668 ambiguous call to overloaded function for a function with string&& and an overload with string_view when using literal strings or pointer variables
With following defined functions
void func(std::string&& s)
{
// use s
}
void func(std::string_view s)
{
// use s
}
and calling:
func("foo");
and
const char* fooval="foo";
func(fooval);
This results in C2668 ambiguous call to overloaded function
It would seem to me that given a choice, the compiler should prefer the string_view variant over the move which requires an implicit string constructor (and memory allocation) (?)
How can I tell VS2022 to ignore the move operator for literal strings and char* variables ?
Upvotes: 1
Views: 1587
Reputation: 2846
If you control the func
API, decide on wether you want to have the string_view
overload or the std::string&&
overload, which actually should be std::string
(without &&
). If you want to sink the string, aka store it or modify it in some way, take std::string
and move it into place if necessary. Otherwise, if you just want to look at the string, take std::string_view
.
Having separate overloads for std::string const&
, std::string&&
, std::string_view
and char const*
is not going to scale, especially with more than one parameter as you will have a combinatorial explosion of overloads.
If you don't control the API you have to accept that it's a bad API and convert your string literal to either std::string
or std::string_view
explicitly.
TL;DR: No additional copies are happening with my suggested approach.
In general I strongly argue that it is hardly ever the right choice to overload functions on std::string_view
and std::string
(whatever the cv-ref qualifications). Either you want to view the string, in that case you take std::string_view
. That is exactly what std::string_view
is designed for and it prevents some copies that could potentially happen, eg. if you pass a string literal to an std::string const&
parameter. And it saves you from having to overload on std::string const&
and char const*
(and any other potential user defined string type).
Otherwise, if you want to sink the string (meaning you want to store it or modify a copy of it) you take std::string
, either by value or by reference. If you take std::string
by reference, you have to overload on std::string const&
and std::string&&
, which is exactly what std::same_as<std::string> auto&&
does. This prevents one move operation compared to taking std::string
by value at the cost of having the additional code complexity of two overloads or the function being a template. The amount of copies happening in either case are the same, i.e. zero, if you already have an std::string
object that you can move from, or one, if you have to create a new std::string
object.
Btw. if you want to sink the string and take it by std::string_view
, you always force a copy, which might be what was happening in your code and gave you the need for the std::string&&
overload.
So the question boils down to wether the simplified code justifies the performance penalty of an additional move operation.
To ultimately answer this question you of course have to profile your application. However some general discussion can be had about it. Here you find a benchmark that compares taking strings by value and by reference. The cost of the additional move operation ranges from 0 to 15 nanoseconds. The cost of the added code complexity can be trivial or substantial, depending on your function and your codebase. Eg. making it a template (with the same_as
approach) forces you to move the definition from a .cpp file to a header. This may or may not be a problem.
However, given the ridiculously small runtime overhead of 15 nanoseconds in the worst case, I would strongly argue that the added code complexity is not worth it until you measure and find the additional move to be a bottleneck of your application, in which case you can still go back to taking the string by reference.
In addition to that, overloading on different reference qualifications is realistically not even always possible in the general case. Assume you want to overload on std::string const&
and std::string&&
. If you have a function taking a single string argument, this may not be a problem. If you take two string arguments, you end up with 4 different overloads. With 4 arguments, you have 16 overloads. The number of overloads you need grows exponentially with the number of parameters, which in most cases is not feasible to maintain. This can of course be addressed with templates, which again forces you to use templates with its own slew of consequences.
Upvotes: 3