5
votes

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?

3

3 Answers

6
votes

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.

2
votes

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.)

1
votes

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.