Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does outputting a class with a conversion operator not work for std::string?

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?

like image 493
chris Avatar asked Jul 09 '13 03:07

chris


2 Answers

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.

like image 95
Yakk - Adam Nevraumont Avatar answered Nov 15 '22 02:11

Yakk - Adam Nevraumont


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.

like image 2
Emilcasvi Avatar answered Nov 15 '22 04:11

Emilcasvi