Why is the Visual C++ compiler calling the wrong overload here?
I am have a subclass of ostream that I use to define a buffer for formatting. Sometimes I want to create a temporary and immediately insert a string into it with the usual << operator like this:
M2Stream() << "the string";
Unfortunately, the program calls the operator<<(ostream, void *) member overload, instead of the operator<<(ostream, const char *) nonmember one.
I wrote the sample below as a test where I define my own M2Stream class that reproduces the problem.
I think the problem is that the M2Stream() expression produces a temporary and this somehow causes the compiler to prefer the void * overload. But why? This is borne out by the fact that if I make the first argument for the nonmember overload const M2Stream &, I get an ambiguity.
Another strange thing is that it calls the desired const char * overload if I first define a variable of type const char * and then call it, instead of a literal char string, like this:
const char *s = "char string variable";
M2Stream() << s;
It’s as if the literal string has a different type than the const char * variable! Shouldn’t they be the same? And why does the compiler cause a call to the void * overload when I use the temporary and the literal char string?
#include "stdafx.h"
#include <iostream>
using namespace std;
class M2Stream
{
public:
M2Stream &operator<<(void *vp)
{
cout << "M2Stream bad operator<<(void *) called with " << (const char *) vp << endl;
return *this;
}
};
/* If I make first arg const M2Stream &os, I get
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(39) : error C2666: 'M2Stream::operator <<' : 2 overloads have similar conversions
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(13): could be 'M2Stream &M2Stream::operator <<(void *)'
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(20): or 'const M2Stream &operator <<(const M2Stream &,const char *)'
while trying to match the argument list '(M2Stream, const char [45])'
note: qualification adjustment (const/volatile) may be causing the ambiguity
*/
const M2Stream & operator<<(M2Stream &os, const char *val)
{
cout << "M2Stream good operator<<(const char *) called with " << val << endl;
return os;
}
int main(int argc, char argv[])
{
// This line calls void * overload, outputs: M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream() << "literal char string on constructed temporary";
const char *s = "char string variable";
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
M2Stream() << s;
// This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
M2Stream m;
m << "literal char string on prebuilt object";
return 0;
}
Output:
M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream good operator<<(const char *) called with char string variable
M2Stream good operator<<(const char *) called with literal char string on prebuilt object
The compiler is doing the right thing:
Stream() << "hello";should use theoperator<<defined as a member function. Because the temporary stream object cannot be bound to a non-const reference but only to a const reference, the non-member operator that handleschar const*won’t be selected.And it’s designed that way, as you see when you change that operator. You get ambiguities, because the compiler can’t decide which of the available operators to use. Because all of them were designed with rejection of the non-member
operator<<in mind for temporaries.Then, yes, a string literal has a different type than a
char const*. A string literal is an array of const characters. But that wouldn’t matter in your case, i think. I don’t know what overloads ofoperator<<MSVC++ adds. It’s allowed to add further overloads, as long as they don’t affect the behavior of valid programs.For why
M2Stream() << s;works even when the first parameter is a non-const reference… Well, MSVC++ has an extension that allows non-const references bind to temporaries. Put the warning level on level 4 to see a warning of it about that (something like “non-standard extension used…”).Now, because there is a member operator<< that takes a
void const*, and achar const*can convert to that, that operator will be chosen and the address will be output as that’s what thevoid const*overload is for.I’ve seen in your code that you actually have a
void*overload, not avoid const*overload. Well, a string literal can convert tochar*, even though the type of a string literal ischar const[N](with N being the amount of characters you put). But that conversion is deprecated. It should be not standard that a string literal converts tovoid*. It looks to me that is another extension by the MSVC++ compiler. But that would explain why the string literal is treated differently than thechar const*pointer. This is what the Standard says: