views:

586

answers:

6

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
A: 

I'm not sure that your code should compile. I think:

M2Stream & operator<<( void *vp )

should be:

M2Stream & operator<<( const void *vp )

In fact, looking at the code more, I believe all your problems are down to const. The following code works as expected:

#include <iostream>
using namespace std;


class M2Stream
{
};

const M2Stream & operator<<( const M2Stream &os, const char *val)
{
    cout << "M2Stream good operator<<(const char *) called with " << val << endl;
    return os;
}


int main(int argc, char argv[])
{
    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;
}
anon
I tried that on VS 2003, no luck in altering the behavior.
Doug T.
Copy and paste failure on my part - try it now.
anon
Well, it works because you removed the member void * operator. I don't have that luxury because the original problem I am trying to solve is with a subclass of basic_ostream<> and that basic_ostream has a member operator that takes void * so I can't remove it.
Carlos A. Ibarra
+1  A: 

The problem is that you're using a temporary stream object. Change the code to the following and it will work:

M2Stream ms;
ms << "the string";

Basically, the compiler is refusing to bind the temporary to the non const reference.

Regarding your second point about why it binds when you have a "const char *" object, this I believe is a bug in the VC compiler. I cannot say for certain, however, when you have just the string literal, there is a conversion to 'void *' and a conversion to 'const char *'. When you have the 'const char *' object, then there is no conversion required on the second argument - and this might be a trigger for the non-standard behaviour of VC to allow the non const ref bind.

I believe 8.5.3/5 is the section of the standard that covers this.

Richard Corden
I'm not sure if this is the case. If I change void* to int* on my PC, it seems to work.
Doug T.
'void*' is special. Most types can convert to it, and I would guess that VC is allowing the conversion to take place for 'const char *' too. If you made the above change with a different compiler then the code would fail to compile.
Richard Corden
Just to clarify - (for some reason I cannot delete my previous comment) - another compiler would not accept your code as written anyway, as 'const char *' to 'void*' is not standard.
Richard Corden
+1. Using a named variable for the stream is the most sensible workaround (even though it feels somewhat inelegant...) And your reasoning about different conversions for the second case sounds plausible to me.
j_random_hacker
+9  A: 

The compiler is doing the right thing: Stream() << "hello"; should use the operator<< 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 handles char 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 of operator<< 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 a char const* can convert to that, that operator will be chosen and the address will be output as that's what the void const* overload is for.

I've seen in your code that you actually have a void* overload, not a void const* overload. Well, a string literal can convert to char*, even though the type of a string literal is char 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 to void*. It looks to me that is another extension by the MSVC++ compiler. But that would explain why the string literal is treated differently than the char const* pointer. This is what the Standard says:

A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type "pointer to char"; a wide string literal can be converted to an rvalue of type "pointer to wchar_t". In either case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex D. ]

Johannes Schaub - litb
+1, thorough as usual :) But could you explain the rationale for making some operator<<()s members and others not? It doesn't serve any purpose that I can see, and breaks some convenient tricks that seem like they should work.
j_random_hacker
@j_random_hacker: Now that sounds like a new SO question!
Richard Corden
I'm not sure about that either :( At least in C++1x, those non-const references will become rvalue references, so we can actually pass temporary streams.
Johannes Schaub - litb
although looking into the latest draft, they reverted that again :( the rvalue refs have become lvalue refs again.
Johannes Schaub - litb
@litb: Thanks, and, well, damn! :(
j_random_hacker
So why doesn't the compiler do me the favor of using its nonstandard extension in the first case also (with the literal string) and bind the non-const "this" reference to the os parameter, and the char array to the const char * of the nonmember overload?
Carlos A. Ibarra
@Charlos, i dunno the behavior of their extensions. Try not to depend on them. Later if you get a more standard compliant compiler, you will get into all sorts of problems then.
Johannes Schaub - litb
Thanks. I reworked the code to avoid the problem by defining some extra <<() overloads for my ostream-derived class.
Carlos A. Ibarra
Doug T.
Also noted in the above link "(I should mention that VC has an evil extension that allows this, but if you compile with /W4 , it warns when the evil extension is activated.)"
Doug T.
Johannes Schaub - litb
+4  A: 

The first problem is caused by weird and tricky C++ language rules:

  1. A temporary created by a call to a constructor is an rvalue.
  2. An rvalue may not be bound to a non-const reference.
  3. However, an rvalue object can have non-const methods invoked on it.

What is happening is that ostream& operator<<(ostream&, const char*), a non-member function, attempts to bind the M2Stream temporary you create to a non-const reference, but that fails (rule #2); but ostream& ostream::operator<<(void*) is a member function and therefore can bind to it. In the absence of the const char* function, it is selected as the best overload.

I'm not sure why the designers of the IOStreams library decided to make operator<<() for void* a method but not operator<<() for const char*, but that's how it is, so we have these weird inconsistencies to deal with.

I'm not sure why the second problem is occurring. Do you get the same behaviour across different compilers? It's possible that it's a compiler or C++ Standard Library bug, but I'd leave that as the excuse of last resort -- at least see if you can replicate the behaviour with a regular ostream first.

j_random_hacker
you got a good explanation there i think so ill give you +1 :)
Johannes Schaub - litb
Thanks! It doesn't happen with the regular ostream (or literal strings would always output as 0x00123456 pointers) but it originally happened to me with a subclass of basic_ostream<>
Carlos A. Ibarra
Sorry, it does happen for the regular ostream. The following outputs a hex address: stringbuf b; ostream( cout << b.str() << endl;
Carlos A. Ibarra
Thanks again for a clear explanation. I had trouble choosing between yours and litb and ultimately chose his answer because of his explanation of the second question, but yours is clearer.
Carlos A. Ibarra
@Carlos: No worries, glad I could help. I've banged my head against this exact problem in the past! :)
j_random_hacker
A: 

EDIT: My post was not the answer to the question. Removed to avoid confusion.

Skizz

Skizz
Another -1 sorry, your reasoning for cases #1 and #3 at least is incorrect. #1: it is not scope that determines that the void* overload should be called, rather it's the fact that the void* overload is a method of ostream (and thus can be legally called on an rvalue object) while the const char* overload is a non-method that takes a non-constant reference to ostream (and thus can't legally bind to an rvalue object). #3: Deleting constant objects??? No, it's because the M2Stream object is now an lvalue and so can bind the non-method overload of >>.
j_random_hacker
Just want to say your answer isn't all bad, you're right to say that at most 1 implicit conversion per parameter will be considered. And C++ name lookup *does* proceed (more or less) from most local to global -- it's just that this lookup doesn't influence which function gets chosen if there are multiple overloads.
j_random_hacker
A: 

You could use an overload such as this one:

template <int N>
M2Stream & operator<<(M2Stream & m, char const (& param)[N])
{
     // output param
     return m;
}

As an added bonus, you now know N to be the length of the array.

Richard Corden
I've tried this particular snippet on GCC, but similar functions work fine on GCC and VC8.