Unexpected overload resolution in visual studio involving void*, string and const char[] - c++

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.

Related

How to avoid `operator[](const char*)` ambiguity?

Consider the following code:
class DictionaryRef {
public:
operator bool() const;
std::string const& operator[](char const* name) const;
// other stuff
};
int main() {
DictionaryRef dict;
char text[256] = "Hello World!";
std::cout << dict[text] << std::endl;
}
This produces the following warning when compiled with G++:
warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
note: candidate 1: const string& DictionaryRef::operator[](const char*) const
note: candidate 2: operator[](long int, char*) <built-in>
I know what this means (and the cause is been explained in operator[](const char *) ambiguity) but I'm looking for a way to ensure correct behavior/resolve the warning without changing my class design - since it makes perfect sense for a class to have both a boolean conversion, and a [](const char*) operator.
What's the purpose of operator[](long int, char*) besides generating random compiler warnings? I can't imagine someone writing 1["hello"] in real code.
Although you "can't imagine someone writing 1["hello"] in a real code", this is something that's legal C++, as a consequence of the commutative [] inherited from C. Reasonable or not, that's how the language is defined, and it's unlikely to be changing for us.
The best way to avoid the ambiguity is to add explicit to the boolean conversion - it's very rare that we ever want a non-explicit operator bool().
An alternative is to replace operator bool() with an operator void*(), which will still satisfy boolean tests, but not convert to integer.

Why does clang take a string literal as a pointer rather than an array?

#include <iostream>
using namespace std;
void f(const char* arg)
{
cout << "arg is a pointer" << endl;
}
template<size_t N>
void f(const char (&arg)[N])
{
cout << "arg is an array." << endl;
}
int main()
{
f("");
}
My compiler is clang 3.8.
The output is:
arg is a pointer
However, according to cppreference.com,
The type of an unprefixed string literal is const char[].
Why does the overload resolution not behave as expected?
It does behave as expected, you just need to adjust your expectations ;-)
const char[1] and const char (&)[1] are different types.
The conversions to const char* (array-to-pointer conversion) and const (&char)[1] (identity conversion) are both considered exact matches, but a non-template is a better match than a template.
If you write a non-template size-specific overload,
void f(const char (&arg)[1])
you will get an error that the function call is ambiguous.
#molbdnilo's answer is correct. To add one detail: Your intuition would be correct and the compiler would prefer to avoid the array-to-pointer conversion by calling the template. But lvalue transformations (lvalue-to-rvalue, array-to-pointer, and function-to-pointer) are specifically ignored in overload ranking, according to [over.ics.rank] ยง13.3.3.2/3.2.1.
There is a workaround: add a fake volatile to restore the balance of overload preference. Just be sure to remove it by const_cast before using the parameter.

Visual C++: forward an array as a pointer

I've cut down some C++ 11 code that was failing to compile on Visual Studio 2015 to the following which I think should compile (and does with clang and gcc):
#include <utility>
void test(const char* x);
int main()
{
const char x[] = "Hello world!";
test(std::forward<const char*>(x));
}
I understand the call to forward isn't necessary here. This is cut down from a much more complex bit of code that decays any arrays in a variadic argument down to pointers and forwards everything on. I'm sure can find ways to work around this with template specialization or SFINAE, but I'd like to know whether it's valid C++ before I go down that road. The compiler is Visual Studio 2015, and the problem can be recreated on this online MSVC compiler. The compile error is:
main.cpp(13): error C2665: 'std::forward': none of the 2 overloads could convert all the argument types
c:\tools_root\cl\inc\type_traits(1238): note: could be '_Ty &&std::forward<const char*>(const char *&&) noexcept'
with
[
_Ty=const char *
]
c:\tools_root\cl\inc\type_traits(1231): note: or '_Ty &&std::forward<const char*>(const char *&) noexcept'
with
[
_Ty=const char *
]
main.cpp(13): note: while trying to match the argument list '(const char [13])'
Update:
#Yakk has suggested an example more like this:
void test(const char*&& x);
int main()
{
const char x[] = "Hello world!";
test(x);
}
Which gives a more informative error:
main.cpp(7): error C2664: 'void test(const char *&&)': cannot convert argument 1 from 'const char [13]' to 'const char *&&'
main.cpp(7): note: You cannot bind an lvalue to an rvalue reference
Again, this compiles on gcc and clang. The compiler flags for Visual C++ were /EHsc /nologo /W4 /c. #Crazy Eddie suggests this might be down to a VC++ extension to pass temporaries as non const references.
To me this looks like a bug in MSVC where it tries to be clever with array-to-pointer and gets it wrong.
Breaking down your second example:
The compiler needs to initialize a const char*&& from an lvalue of type const char[13]. To do this, 8.5.3 says it creates a temporary of type const char* and initializes it with the const char[13], then binds the reference to the temporary.
Initializing a const char* from a const char[13] involves a simple array-to-pointer conversion, yielding a prvalue of const char* which is then copied into the temporary.
Thus the conversion is well defined, despite what MSVC says.
In your first example, it's not test() that is causing the issue, but the call to std::forward. std::forward<const char*> has two overloads, and MSVC is complaining neither is viable. The two forms are
const char*&& std::forward(const char*&&);
const char*&& std::forward(const char*&);
One takes an lvalue reference, one takes an rvalue reference. When considering whether either overload is viable, the compiler needs to find a conversion sequence from const char[13] to a reference to const char*.
Since the lvalue reference isn't const (it's a reference to a pointer to a const char; the pointer itself isn't const), the compiler can't apply the conversion sequence outlined above. In fact, no conversion sequence is valid, as the array-to-pointer conversion requires a temporary but you can't bind non-const lvalue references to temporaries. Thus MSVC is correct in rejecting the lvalue form.
The rvalue form, however, as I've established above, should be accepted but is incorrectly rejected by MSVC.
I believe std::decay<const char []>::type is what you're looking for http://en.cppreference.com/w/cpp/types/decay
I think it should compile, but why are you bothering to use std::forward?
Isn't the correct solution simply to replace
std::forward<const char*>(x)
with:
(const char*)x
or for the generic case, replace:
std::forward<decay_t<decltype(x)>>(x)
with:
decay_t<decltype(x)>(x)
Using std::forward doesn't seem to have any purpose here, you have an array, you want to decay it to a pointer, so do that.

Force `const char[]` string literals in clang

Compiling the following code
void f(char *, const char *, ...) {}
void f(const char *, ...) {}
int main()
{
f("a", "b");
}
with clang gives me this error:
prog.cpp:6:2: error: call to 'f' is ambiguous
f("a", "b");
^
prog.cpp:1:6: note: candidate function
void f(char *, const char *, ...) {}
^
prog.cpp:2:6: note: candidate function
void f(const char *, ...) {}
^
AFAIK string literals are constant in C++, and so the overload rules should drop the first variant from consideration, thus unambiguously resolving to the 2nd variant. But I guess that Clang makes them non-const for compatibility reasons (I know MSVC does that too).
What compiler flags to use to fix this? I'm already compiling with -std=c++11.
EDIT: Explicit cast to const char* solves this:
f((const char*)"a", "b");
But if I'm correct on that the observed compiler behaviour isn't standard, I want to fix the compiler behaviour rather than the standard conforming code.
I think this is a bug. Conversion of string literals to char * was removed in C++11 and I am not aware of any provision in overload resolution for a conversion sequence involving it.
As a workaround that does not involve changing every single call to f, you can write another overload that explicitly catches every call with a string literal, by capturing the array by reference:
template<size_t N, typename ...F>
void f(char const (&a)[N], F&&... args)
{
f((char const *)a, std::forward<F>(args)...);
}

C++ const char* overloading confusion

I don't understand why this program produces the output below.
void blah(const char* ) {printf("const char*\n");}
void blah(const std::string&) {printf("const string ref\n");}
template<class t>
void blah(t) {printf ("unknown\n");}
int main(int, char*)
{
blah("hi");
char a[4];
blah(a);
std::string s;
blah(s);
getch();
}
Outputs:
const char*
unknown
const string
In VS2008. It is willing to convert the std::string to a const reference, but why won't it convert the char* to a const char* and use the overload?
The type of "hi" is const char[3], whereas the type of a is char[4].
So, the first call requires only array-to-pointer conversion (aka "decay"). The third call requires only binding an object to a reference-to-const (I don't think "converting" is the correct terminology for reference-binding, although I may be mistaken). The second call would require array decay and a pointer conversion in order to call the const char* overload.
I claim without actually checking the overload resolution text in the standard that this extra step is what makes the template a better match than the const char* overload.
Btw, if you change "unknown\n" to "%s\n", typeid(t).name() then you can see what the type t was deduced as. For your code, it is deduced as char* (because arrays can't be passed by value), but see what happens if you change the template to take a t& parameter instead of t. Then t can be deduced as char[4].