views:

131

answers:

2
+1  Q: 

C++ overloading

The following code is giving me a compilation error. Can anyone please tell me why?

class mytype {
public:
    int value;
    mytype(int a) {
        value = a;
    }
    friend ostream& operator<<(ostream& stream, const mytype& a) {
        stream << a.value;//works
        return stream;
    }
    friend ostringstream& operator<<(ostringstream& stream, const mytype& a) {
        stream << (a.value);//compilation error
        return stream;
    }
};

Error:

error C2027: use of undefined type 'std::basic_ostringstream<_Elem,_Traits,_Alloc>'

Upon fixing that:

error C2666: 'operator <<' : 18 overloads have similar conversions

Final fix:

Declare constructor as explicit. Works on MSVC then.

I wonder why.

+6  A: 

error C2027: use of undefined type 'std::basic_ostringstream<_Elem,_Traits,_Alloc>'

You need #include <sstream> to get the [i/o]stringstream classes.

About the other errors

The problem with an overload of the form

ostringstream& operator<<(ostringstream& stream, const mytype& a)

is that it matches an ostringstream exactly. Why is it a bad thing to have a more exact overload? From the standard, §13.3.3/1-2:

a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2) …

If there is exactly one viable function that is a better function than all other viable functions, then it is the one selected by overload resolution; otherwise the call is ill-formed

Therefore if operator<<( ostream &, int ) and operator<<( ostringstream &, mytype const & ) are both candidates for stream << value, the former matches int exactly with no conversion but the latter matches ostringstream exactly. Therefore neither can be "not worse" for all arguments, and neither candidate may be chosen.

But the code is valid because of a loophole. Your overload is not a candidate at all, except when you actually use your type in the function call. When you declare/define a friend inside a class block, it does not introduce it to any namespace scope; it merely associates it with the class scope, which allows it to be found if that class type describes one of the arguments being passed.

The standard has this to say about friend declarations, although it's in another section (14.6.5):

Friend declarations do not introduce new names into any scope…

So, MSVC tried to be nice and proactively introduced your friend to its enclosing namespace. Strike against MSVC.

However, when I attempted to add a declaration equivalent to what MSVC did "for free," Comeau and GCC nicely resolved the resulting overloading conflict — strike against them. Or is it? As it turns out, the overloaded call occurs earlier in the file than my recommended declaration. If I move the declaration before class mytype { (which requires forward-declaring mytype), then both properly complain about the ambiguity.

Using an overload before it is declared in namespace scope appears to be well and good according to §3.3-3.4 of the Standard. So actually GCC and Comeau were both in the right. The point of declaration is right after the name of the declared object. (And last I checked, self-referential function declarations can still crash GCC.) ADL invokes unqualified lookup into the enclosing namespace at a point immediately before the enclosing class. (3.4.1/8 final bullet, 3.4.1/9, 3.4.2/2a.) If the friend hasn't been declared before the class, it's legitimately not a candidate. (7.3.1.2/3) Isn't C++ a beautiful language?

How keep the simplified example on GCC, but break subsequent code.

    friend ostringstream& operator<<(ostringstream& stream, const mytype& a) {
        stream << (a.value);//compilation error
        return stream;
    }
};

ostringstream& operator<<(ostringstream& stream, const mytype& a); // <- here

Following this declaration, it will be impossible to write an int into an ostringstream.

How to break everything uniformly, with simpler declaration semantics.

class mytype; // <- here
              // and here:
inline ostringstream& operator<<(ostringstream& stream, const mytype& a);

class mytype {
public:

Following this declaration, it will be impossible to write an int into an ostringstream… including the friend declarations inside class mytype {}.

Actual solution.

The stream classes are supposed to be indistinguishable. If you really want to determine whether a given stream feeds a string in memory (and you shouldn't), it's best to look at its internal streambuf object, returned by rdbuf(), which actually performs the I/O gruntwork. Even a generic ostream object can have ostringstream functionality if given a stringbuf.

 if ( typeid( stream.rdbuf() ) == typeid( stringbuf * ) ) {
     // this is effectively a stringstream
 } else {
     // not a stringstream
 }
Potatoswatter
Yeah I did that and now I get this error.error C2666: 'operator <<' : 18 overloads have similar conversions
Meher
@Meher: And what kind of lines of code do those errors/overloads come from?
Potatoswatter
From the line: stream << (a.value);
Meher
@Meher: Does it say anything about what the similar overloads are? Probably there is some problem with a custom/third-party library you're using. It might help to start with an simple test file (such as the code I linked, http://ideone.com/lt9wb) and add your headers to it one by one.
Potatoswatter
Meher
I am using only the C++ standard library headers.
Meher
I get the exact same error with the stripped down code from IDEone.
Meher
@Meher: Unfortunately I don't use MSVC. I can't say what could cause a problem like that. Have you been using that installation for C++ code? Does it work on other machines in the office?
Potatoswatter
@greyfade: No. Everything related to mytype has been put up in the question.
Meher
Try using different class name .. May be its conflicting with any third party library you are using?
Xinus
I'll try running my code on G++. Probably should work there.
Meher
@Xinus: No I have not used the name mytype anywhere else in the code. Even the code that potatoswatter put up on IDEone doesn't seem to work on MSVC++.
Meher
I am stumped. MSVC 2010 throws fits with the line `stream << (a.value);` in the `ostringstream` overload. Explicitly casting `stream` to an `ostream
James McNellis
Making the `mytype` constructor `explicit` also fixes the issue. I still don't see why. The user-defined conversion should be a worse match than the derived-to-base conversion, right?
James McNellis
@James: don't ask me, I don't have a Windows installation at all since Parallels 3 went obsolete. I should probably get a copy of VMWare Fusion. Comeau accepts it too, FWIW.
Potatoswatter
In terms of the standard, the user-defined int->mytype conversion seems to cause confusion among the various standard conversions on the second operand, when the only necessary conversion is the pointer-to-base-pointer standard conversion for the first operand.
Potatoswatter
@Meher: Oooh! Try declaring the overload so it doesn't have to be found by ADL! See updated answer.
Potatoswatter
If the overload is declared at namespace scope, then even Comeau rejects it. I don't know enough about the distinctions between defining as a friend and defining at namespace scope to understand why the former should work and the latter should not.
James McNellis
@James: Are you sure you pasted properly? If I add that one line where indicated, G++ and Comeau both accept it. The distinction is that declaring it at namespace scope injects the name into the namespace. If you don't, it's floating in the ADL no-man's land without any qualified name.
Potatoswatter
Oh, my bad; I had moved the definition into namespace scope.
James McNellis
Ah ha. Now I understand: moving the namespace-scope overload declaration to before the class definition gives effectively the same result of defining the overload after the class definition. I was thoroughly confused as to why what you had done worked but what I had done didn't. Gotta love undefined behavior. Ah well; if it was any easier, it wouldn't be C++... :-)
James McNellis
@James: Love it or hate it. The pleasure I get from figuring this out is highly irrational. :v)
Potatoswatter
@James: Do you know where that UB rule is? I can't even think of what to search for…
Potatoswatter
@James: Correction: No UB needed. It's just semantically messy.
Potatoswatter
+2  A: 

There is an overload ambiguity in the call to operator<< in the ostringstream overload.

stream << a.value;

There are a number of operator<< overloads that are members of the ostream class, which is a base class of ostringstream. One of these is declared as:

ostream& ostream::operator<<(int);

This overload is an exact match for the right-hand side (a.value is an int) but requires a derived-to-base conversion on the left-hand side (stream is an ostringstream, which is derived from ostream).

However, there is also your ostringstream overload:

ostringstream& operator<<(ostringstream&, const mytype&);

This overload is an exact match for the left-hand side (stream is an ostringstream), and a user-defined converting constructor (your mytype(int) constructor) can be used to convert a.value (an int) to a mytype.

Since one overload matches the first argument better and the other overload matches the second argument better, there is an ambiguity. You can fix this either by:

  • Explicitly converting the left-hand side to an ostream (using (ostream&)stream = a.value;), or
  • Remove the user-defined conversion by making the constructor explicit.
James McNellis
[Note: I'm not convinced that this is 100% correct; see the discussion in reply to Potatoswatter's answer. Specifically, I'm not entirely familiar with the differences between defining as a friend and defining at namespace scope. I'm researching that.]
James McNellis
+1… MSVC is right, Comeau is wrong (13.3.2/2 appears to require a diagnostic). If one function provides a uniquely best conversion for one argument, it must also be the best candidate overall. The given `ostringstream` overload breaks that with `int`.
Potatoswatter
@James: actually, funny, I didn't try to integrate that discussion with your answer until reading that comment :vP . MSVC *is* incorrect in rejecting the code *without* the extra declaration because it shouldn't find the overload when looking for candidates for `stream << int`, since that doesn't point ADL at the OP's class. So my suggestion should break it for all platforms. But like I said, it passes Comeau and GCC for me…
Potatoswatter