6

This works, printing 1:

#include <iostream>

struct Int {
    int i;
    operator int() const noexcept {return i;}
};

int main() {
    Int i;
    i.i = 1;
    std::cout << i;
}

However, this fails to compile on GCC 4.8.1:

#include <iostream>
#include <string>

struct String {
    std::string s;
    operator std::string() const {return s;}
};

int main() {
    String s;
    s.s = "hi";
    std::cout << s;
}

Here are the relevant parts of the error:

error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream}’ and ‘String’)
std::cout << s;

snip

template std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::basic_string<_CharT, _Traits, _Alloc>&)
operator<<(basic_ostream<_CharT, _Traits>& __os,

/usr/include/c++/4.8/bits/basic_string.h:2753:5: note: template argument deduction/substitution failed:
main.cpp:25:18: note: ‘String’ is not derived from ‘const std::basic_string<_CharT, _Traits, _Alloc>’
std::cout << s;

I only use std::cout and std::string, which have the same template arguments. I'm really not sure why this wouldn't be able to pick up the implicit conversion like it did for Int. Why does it work with int, but not std::string?

chris
  • 60,560
  • 13
  • 143
  • 205

2 Answers2

8

That operator is a free template function. User defined conversions do not get checked when matching against a template function arguments, it instead uses type pattern matching (substitution).

In theory a SFINAE overload using std::is_convertable<> would be able to do what you want, but that technique was not used when operator<< that outputs a std::string to a basic_ostream<char> was defined.

A manual overload to output your class to basic_ostream<...> will fix your problem.

I would do this:

struct String {
  std::string s;
  operator std::string() const {return s;}
  friend std::ostream& operator<<( std::ostream& os, String const& self) {
    return os<<self.s;
  }
};

which has the added benefit of not creating a wasted copy.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That's kind of a bummer, but at least overloading it yourself with a `static_cast` isn't much work. – chris Jul 09 '13 at 04:07
  • @chris I would instead overload `<<` -- or write `to_string` as a free function (C++1* style) – Yakk - Adam Nevraumont Jul 09 '13 at 04:08
  • That's what I meant, overloading `operator<<` to just `return out << static_cast(s);`. Really not much at all, it just feels a bit unnecessary. – chris Jul 09 '13 at 04:10
  • If `operator << ( std::basic_ostream< ... > &, std::basic_string const & )` were an inline, non-template friend of `basic_string`, it would also work. That is probably a better, cleaner practice than SFINAE. – Potatoswatter Jul 09 '13 at 05:34
  • @potatoswatter but then it would not transparently work with custom char traits `basic_string` and `basic_ostream`. SFINAE would have other problems to -- it is too attractive. If we used `identity<>::type` to block deduction on the string type, it might pick up conversions: but what if someone has a csst-to-ostream object? Hurm. – Yakk - Adam Nevraumont Jul 09 '13 at 10:41
  • 1
    @Yakk Yes it would work with custom traits (not that they are useful anyway). A non-template friend defined inside a class template is effectively templated without being an instantiation. The specialization rules don't apply at all. The catch is that the function can't be instantiated, it's only generated as a tricky side effect of class instantiation, and it can only be found by ADL. The construct is ideal for operator overloads. – Potatoswatter Jul 09 '13 at 14:39
2

The << operator seems to have a pool of overloads with types other than std::string. as I have seen by using the clang++ compiler.

The compiler does the implicit conversion from String to std::string but it does not match any of the defined << operators.

If you define the << operator for std::string it will work

#include <iostream>
#include <string>

std::ostream& operator<<(std::ostream& s, const std::string& str)
{
        s << str.c_str();
        return s;
}

struct String {
    std::string s;
    operator std::string() const {return s;}
};

int main() {
    String s;
    s.s = "hi";
    std::cout <<  s;
}

You can find more details on the same issue here: http://forums.codeguru.com/showthread.php?432227-RESOLVED-Implicit-conversion-to-std-string

As seen in one post;

The problem is the operator<< here is a template and no template instantiations can be made for the type TestClass since the user defined conversions are probably not being considered in argument deduction for templates for implicit instantiations (atleast I could not find in section 14.7.1 (Implicit instantiation). This results in an empty overload set for the call "std::cout << obj << '\n';" and hence the error. It does not matter if an instantiation already happened or not. Template candidates are chosen into overload set on exact matches (except for array to pointer decay and const qualification - http://groups.google.co.in/group/com...29910b6?hl=en&).

When you provide an explicit overload operator<< with type std::string, it is non-template and adds up in the overload set and hence invoking the implicit conversion while doing overload resolution/a callable match.

Emilcasvi
  • 131
  • 3
  • The one for `std::string` is in ``. I believe the reason this one works is that it's more specialized, so has priority, and from Yakk's answer, doesn't have to deduce template arguments. – chris Jul 09 '13 at 04:12
  • Yes it is, but seems to be a template that it is not initialized when you perform the first implicit conversion as you can see in the link I gave you. – Emilcasvi Jul 09 '13 at 04:17
  • Well, the other problem is that we're not allowed to overload operators to take standard types like that. – chris Jul 09 '13 at 04:20
  • Here you have the same problem resolved: http://stackoverflow.com/questions/13883067/implicit-conversion-to-stdstring, you can also provide a conversion to const char* that will work for the cout operator, or the static_cast to make the compiler to instantiate the correct << function. – Emilcasvi Jul 09 '13 at 04:34
  • I suppose you could. I usually prefer the good types when I have a conversion operator like that so that there's one less user-defined conversion between my type and whatever it's being used in. That can make the difference of having to cast it or store it in an intermediary variable. The chances of using it for something that takes a `std::string` are, imo, higher than that of `const char *`. Of course for this example, doing both works as well. – chris Jul 09 '13 at 04:42