开发者

Overload resolution failure when streaming object via implicit conversion to string

Disclaimer: I know that implicit conversion to string should be avoided, and that the proper approach would be an op<< overload for Person.


Consider the following code:

#include <string>
#include <ostream>
#include <iostream>

struct NameType {
   operator std::string() { return "wobble"; }
};

struct Person {
   NameType name;
};

int main() {
   std::cout << std::string("bobble");
   std::cout << "wibble";

   Person p;
   std::cout << p.name;
}

It yields the following on GCC 4.3.4:

prog.cpp: In function ‘int main()’:
prog.cpp:18: error: no match for ‘operator<<’ in ‘std::cout << p.Person::name’
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:112: note: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>& (*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:121: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:131: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:169: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:173: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:177: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/ostream.tcc:97: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:184: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/ostream.tcc:111: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:195: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:204: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:208: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::op开发者_开发知识库erator<<(long long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:213: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:217: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:225: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/ostream:229: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char, _Traits = std::char_traits<char>]
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/ostream.tcc:125: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_streambuf<_CharT, _Traits>*) [with _CharT = char, _Traits = std::char_traits<char>]

How come the free op<<(ostream&, string const&) doesn't make it into the overload set? Is this due to a combination of the desired overload being a template instantiation and ... ADL?


14.8.1/4 in C++98

Implicit conversions (clause 4) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.

Here you would like an instantiation of

template <class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&,
               const basic_string<charT, traits, Allocator>&);

to be deduced without providing explicitly any template arguments. So all of the arguments contain a template-parameter that participates in the template argument deduction and thus none of them can get its value from an implicit conversion.


It is because it is a template.

For this to work you would need to instantiate the template first, and use the conversion operator afterwards. That's the wrong order, so it doesn't work.


It doesn't matter if you have used a specific operator earlier in the program or not. Each use is considered separately

The overloads considered as candidates are those where all the template parameters can be deduced from std::ostream, or those that are members of that class.


What if we add a non-template operator?

#include <string> 
#include <ostream> 
#include <iostream>  

struct NameType {
   operator std::string() { return "wobble"; } 
};  

struct Person {
    NameType name;
};  

void operator<<(std::ostream& os, const std::string& s)   // ** added **
{ std::operator<<(os, s); }

int main() 
{    
    std::cout << std::string("bobble");
    std::cout << "wibble";

     Person p;
     std::cout << p.name; 
}  

Now it works, and outputs

 bobblewibblewobble


Its because user-defined conversion function isn't consider in ADL. ADL means overload set contains overload function(s) from the namespace in which the argument is defined. Here the type of argument to operator<< is NameType but operator << (std::ostream&, const NameType&) has not been defined in the namespace in which NameType is defined. Hence the error, as searching for appropriate overload stops right there. That is what ADL is. ADL doesn't go further to look into the definition of NameType to determine if it defines any user-defined conversion function or not.

You will get the same error if you do the following:

NameType name;
std::cout << name ; //error: user-defined conversion not considered.

You need to cast it:

std::cout << (std::string)name << std::endl; //ok - use std::string()

Also, you might have more than one user-defined conversion functions:

std::cout << (int)name << std::endl; //ok - use int() instead

Output at ideone:

wobble
100


The conversion to string is only invoked if some cases:

a) requested explicitly (string) p.name

b) assignment to a string string a = p.name

c) ...

If the present case does not fit any, you can force the invokation to ostream<<(ostream&,string) in at least two ways:

  1. http://ideone.com/SJe5W Making NameType be an string (by public inheritance).

  2. go to case a): requesting explicitly the conversion as seen in the example with the conversion to (int).

I really prefer the option 1.


That's because user defined conversions cannot be chained. To explain with an example:

struct A {
  void operator = (const int i);
};

struct B {
  operator int ();
}

A a;
B b;
a = b;  // error! because, compiler will not match "A::operator=" and "B::operator int"

Here is the similar question, I asked sometime back.

In your case, your first user defined conversions are,

(1) NameType::operator std::string()

(2) operator <<(ostream&, const std::string&) which is somewhat like ostream::operator<<(std::string&).

When you write, cout << p.name; Now two type of objects come face to face:

ostream (LHS) <====> NameType (RHS)

Now, operator <<(ostream&, const string&) is invoked only if RHS is string. But here it's NameType; so it's not invoked.

And, NameType::operator string () is invoked only if, LHS is string. But here it's ostream; so it's not invoked.

To make this equation true; bot the of the above operator methods should be invoked by compiler. But that's not supported by C++. Why it's not supported, is described in the link I posted above.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜