ForEveR
ForEveR

Reputation: 55887

Conversion operators

This code is not compilable. I can't find why in standard. Can someone explain?

#include <iostream>
#include <string>

template<typename T>
class S
{
public:
   explicit S(const std::string& s_):s(s_)
   {
   }
   std::ostream& print(std::ostream& os) const
   {
      os << s << std::endl;
      return os;
   }
private:
   std::string s;
};

template<typename T>
std::ostream& operator << (std::ostream& os, const S<T>& obj)
{
   return obj.print(os);
}

/*template<>
std::ostream& operator << <std::string> (std::ostream& os, const S<std::string>& obj)
{
   return obj.print(os);
}*/

class Test
{
public:
   explicit Test(const std::string& s_):s(s_)
   {
   }
   //operator std::string() const { return s; }
   operator S<std::string>() const { return S<std::string>(s); }
private:
   std::string s;
};

int main()
{
   Test t("Hello");
   std::cout << t << std::endl;
}

Compiler output:

source.cpp: In function 'int main()':
source.cpp:47:17: error: no match for 'operator<<' in 'std::cout << t'
source.cpp:47:17: note: candidates are:
In file included from include/c++/4.7.1/iostream:40:0,
                 from source.cpp:1:
include/c++/4.7.1/ostream:106:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
include/c++/4.7.1/ostream:106:7: note:   no known conversion for argument 1 from 'Test' to 'std::basic_ostream<char>::__ostream_type& (*)(std::basic_ostream<char>::__ostream_type&) {aka std::basic_ostream<char>& (*)(std::basic_ostream<char>&)}'
....

Upvotes: 1

Views: 633

Answers (2)

jpalecek
jpalecek

Reputation: 47762

Thats because no conversions, except for array-to-pointer, function-to-pointer, lvalue-to-rvalue and top-level const/volatile removal (cf. c++11 or c++03, 14.8.2.1), are considered when matching a template function. Specifically, your user-defined conversion operator Test -> S<string> is not considered when deducing T for your operator<< overload, and that fails.

To make this universal overload work, you must do all the work at the receiving side:

template <class T>
typename enable_if<is_S<T>::value, ostream&>::type operator <<(ostream&, const T&);

That overload would take any T, if it weren't for the enable_if (it would be unfortunate, since we don't want it to interfere with other operator<< overloads). is_S would be a traits type that would tell you that T is in fact S<...>.

Plus, there's no way the compiler can guess (or at least it doesn't try) that you intended to convert Test to a S<string> and not S<void> or whatever (this conversion could be enabled by eg. a converting constructor in S). So you have to specify that

  • Test is (convertible to) an S too
  • the template parameter of S, when converting a Test, is string

template <class T>
struct is_S {
  static const bool value = false;
};

template <class T>
struct is_S<S<T>> {
  static const bool value = true;
  typedef T T_type;
};

template <>
struct is_S<Test> {
  static const bool value = true;
  typedef string T_type;
};

You will have to convert the T to the correct S manually in the operator<< overload (eg. S<typename is_S<T>::T_type> s = t, or, if you want to avoid unnecessary copying, const S<typename is_S<T>::T_type> &s = t).

Upvotes: 4

aschepler
aschepler

Reputation: 72271

The first paragraph of @jpalecek's answer explains what the issue is. If you need a workaround, you could add a declaration like:

inline std::ostream& operator<< (std::ostream& os, const S<std::string>& s)
{ return operator<< <> (os, s); }

Since that overload is not a template, implicit conversions to S<std::string> will be considered.

But I can't see any way to do this for all types S<T>...

Upvotes: 1

Related Questions