22

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>::operator<<(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?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055

5 Answers5

22

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.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • 1
    This language is so hard to follow. So "the parameter type" here is `std::basic_string` (for some specialisation), and this type of course contains `template-parameters` that participate in deduction because I am relying on deduction. Bleh! – Lightness Races in Orbit Sep 26 '11 at 13:58
8

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
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 2
    It is instantiated, by the first line in `int main()`. No? – Lightness Races in Orbit Jul 13 '11 at 10:01
  • 1
    Thanks for your edit. I'm still not convinced; the previous template instantiation should have brought a viable candidate (which takes `std::string`) into existence. Your working example features a change in namespace as well as non-template-ness, which would fit with Nawaz's convincing ADL theory. – Lightness Races in Orbit Jul 13 '11 at 12:58
  • This is the same namespaces that were searched before, that of `cout` and that of `NameType`. What I have shown here is that the user defined conversion operator **is used** when the candidate is a non-template, but the output operator is **not even a candidate** when it is a template. – Bo Persson Jul 13 '11 at 13:49
  • 1
    It would be really terrible if the fact that the template had been instantiated by an unrelated earlier use changed the set of candidates for the overload resolution. Suddenly deleting dead code that happens to instantiate some needed templates could cause lots of confusing error messages. – bames53 Sep 20 '11 at 16:48
  • just for accuracy, `operator<<` should return std::ostream& (actually `std::basic_ostream`) – Andriy Tylychko Sep 22 '11 at 13:46
  • @Andy - That is correct for the general case, to enable chaining, but in this example it doesn't matter. – Bo Persson Sep 22 '11 at 15:43
2

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
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    Can you explain how ADL is coming into the picture here, and why the user-defined conversion is not considered? – Lightness Races in Orbit Jul 13 '11 at 10:01
  • I know it works without the implicit conversion. I'm trying to work out why it doesn't with. – Lightness Races in Orbit Jul 13 '11 at 10:04
  • @Tomalak: Added explanation. :-) – Nawaz Jul 13 '11 at 10:20
  • My mind is a bit sore, but I think I follow. Add some standard references for a gold star. :) For my own peace of mind, may I verify that I've understood correctly: the `op<<` overload I want is found not in `NameType`'s namespace, but `std::string`'s (i.e. `std`). And overload resolution for a user-defined conversion doesn't go far enough to find it. Yea? – Lightness Races in Orbit Jul 13 '11 at 10:37
  • 1
    Hang on, why isn't `std` considered? The LHS (`std::cout`) ought to bring that into the overload set via ADL? – Lightness Races in Orbit Jul 13 '11 at 10:46
  • @Tomalak: User-defined conversion is out of the question. What here applies is ADL. And since the type of the argument is `NameType`, it searched for `operator << (std::ostream&, const NameType&)` which has not been defined. It doesnt search for `std::string` version of `operator<<`. – Nawaz Jul 13 '11 at 10:46
  • @Tomalak: `std` is consider, but even this doesn't has *appropriate* overload. – Nawaz Jul 13 '11 at 10:51
  • @TomalakGeret'kal let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1405/discussion-between-nawaz-and-tomalak-geretkal) – Nawaz Jul 13 '11 at 10:51
  • *"[...] 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."*, Wrong, `std` is also searched (because type of first argument is in `std`) and as appropriate overload is looked up `NameType` is searched for suitable conversion to parameter in that overload, this has nothing to do with ADL. I have simple [proof](http://ideone.com/mMEhn7): create class with conversion operator to `int` and try to print it with `ostream`. – PcAF Jun 28 '16 at 19:53
0

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.

robermorales
  • 3,293
  • 2
  • 27
  • 36
-3

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.

Community
  • 1
  • 1
iammilind
  • 68,093
  • 33
  • 169
  • 336