5

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 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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

8 Comments

+1 (especially for the general rule), but I can see why it's unexpected; because it also relies on the unfortunate const-dropping conversion from string literal to char*.
The issue in the question is the conversion to pointer to non-const, which shouldn't be allowed.
@Angew But the const-dropping is still not a user defined conversion. (In a more general context, I can see why it is unexpected, because logically, string literals and std::string are strings, and one doesn't think of a conversion being necessary. Alas, for historical reasons...)
@JamesKanze I know, but even if one's aware that string literals are not std::strings, it's still confusing that MSVC applies the const-drop without so much as a warning.
@interjay My reading is that it shouldn't be allowed: "This conversion is considered only when there is an explicit appropriate pointer target type,[...]" (C++03, §4.2/2). I would interpret "appropriate pointer target type" to mean a char* (and only a char*) for a narrow string literal. At any rate, C++11 bans it completely, even for a char*.
|
1

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.

4 Comments

@dyp good point. Ambiguous in terms of programmer intent then?
Yeah, maybe. It's either quite a subtle overload resolution (and therefore error-prone) or not expressing the intent of the programmer.
Btw. there's another typo in coid f(std::string const& s, std::true_type){ blah } (shall I fix those kind typos?)
@DyP Sure, but really I should compile my answers. :) I need to find a good way to set up a compiler on my phone, or a web compiler that works well on a phone (many of them have quirky edit boxes that don't play well with touch screen browsers).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.