I got following unexpected overload resolution behavior with the visual studio compiler (tested in VS2010 and VS2012).
Minimal example:
#include <iostream>
#include <string>
void f(void *)
{
std::cout << "f(void*)\n";
}
void f(const std::string &)
{
std::cout << "f(const std::string &)\n";
}
int main()
{
f("Hello World!");
}
Output:
> f(void *)
Expected Ouptut:
> f(const std::string &)
Compiling with GCC(tested with 4.6.3) generates the expected output.
If I comment out the "const std::string &" version of f(), visual studio happily compiles on /W4 without any warnings, while GCC emits following error (as expected): "invalid conversion from 'const void*' to 'void*' [-fpermissive]".
Does anyone know why visual studio behaves in that way, choosing basically a const cast overload over a conversion to std::string for char[]?
Is there any way to prohibit this behavior, or at least get VS to generate a warning?
For VS 2013 Microsoft documents silently dropping const
for string literals as a Microsoft-specific behavior for C++:
Microsoft Specific
In Visual C++ you can use a string literal to initialize a pointer to non-const char or wchar_t. This is allowed in C code, but is deprecated in C++98 and removed in C++11.
...
You can cause the compiler to emit an error when a string literal is converted to a non_const character when you set the
/Zc:strictStrings
(Disable string literal type conversion) compiler option.
For versions earlier than VS 2013 (for example VS 2012's documentation), Microsoft documents string literals in C++ as using the C convention of being non-const array of char
.
I don't see why it is unexpected. The conversion of char
const[]
to a std::string
involves a user defined conversion;
the conversion to void*
doesn't. And a conversion involving
a user defined conversion is always "less good" than one which
doesn't involve a user defined conversion.
The real issue here is that C++ doesn't have a built-in string
type, and the string literals don't have type std::string
.
The normal solution is to provide an overload for char const*
as well:
void f( void* );
void f( std::string const& );
inline void f( char const* p ) { f( std::string( p ) ); }
This additional overload will pick up string literals.
(As a general rule: anytime you're overloading: if one of the
overloads is for std::string
, provide one for char const*
as
well, if any are for arithmetic types, provide one for
int
, to catch integral literals, and if any are for a floating
point type, provide one for double
, to catch floating point
literals.)
The apparent issue is, as noted by others, that MSVC allows implicit conversion from string literals to non-const
char*
, and thence to void*
.
I say apparent because your void*
overload should be a void const*
overload, as it does not change the pointed to data. Doing so will make things 'worse', as calling it with a string literal will now unambiguously select the void const*
overload. However this illustrates what is going wrong: ""
is a char const(&)[1]
(an array of const char
), not a std::string
, and char const(&)[1]
is closer related to pointers than to std::string
. Relying on the overload picking std::string
over a pointer is fragile, even on gcc, as making your code const
correct breaks it!
To fix this we can write a greedy overload for std::string
.
template<typename S, typename=typename std::enable_if<std::is_convertible<S,std::string>::value>::type>
void f(S&&s){
f(std::string{std::forward<S>(s)});
}
with the above two overloads left intact (except const
added).
Or (better) via tag dispatching:
void f(void const* v, std::false_type){
std::cout << "f(void*)\n";
}
void f(std::string const& s, std::true_type){
std::cout << "f(const std::string &)\n";
}
template<typename T>
void f(T&&t){
return f(std::forward<T>(t), std::is_convertible<T,std::string>() );
}
both of which are ways to do manual function overload dispatching, biased towards std::string
.
live example
Note that std::string
literals are now possible in C++, but I would advise against requiring them on the basis of fragility.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With