4

This question follows my previous question : Generic operator<< ostream C++ for stringifiable class where I would like to implement a generic <<ostream operator which would work for any class which owns a to_str() method.

I have succeeded checking whether a class implements a to_str() method and use std::cout << stringify(a) thanks to this answer. However, I have difficulties writing template ostream<< operators to make std::cout << a works.

The following test code :

#include <iostream>
#include <sstream>
#include <string>

template<class ...> using void_t = void;

template<typename T, typename = void>
struct has_to_string
: std::false_type { };

template<typename T>
struct has_to_string<T, 
    void_t<decltype(std::declval<T>().to_str())>
    >
: std::true_type { };

template<typename T> std::enable_if_t<has_to_string<T>::value, std::string> 
stringify(T t) { 
    return t.to_str(); 
} 

template<typename T> std::enable_if_t<!has_to_string<T>::value, std::string> 
stringify(T t) { 
    return static_cast<std::ostringstream&>(std::ostringstream() << t).str(); 
} 

// The following does not work
/*
template<typename T> std::enable_if_t<has_to_string<T>::value, std::ostream&> 
operator<<(std::ostream& os, const T& t) {
    os << t.to_str();
    return os;
}

template<typename T> std::enable_if_t<!has_to_string<T>::value, std::ostream&> 
operator<<(std::ostream& os, const T& t) {
    os << t;
    return os;
}
*/

struct A {
    int a;
    std::string to_str() const { return std::to_string(a); }
};

struct B {
    std::string b;
    std::string to_str() const { return b; }
};

int main() {
    A a{3};
    B b{"hello"};
    std::cout << stringify(a) << stringify(b) << std::endl;    // This works but I don't want to use stringify
    // std::cout << a << b << std::endl;               // I want this but it does not work
}

gives the same error as in the original question. What am I doing wrong ?

Community
  • 1
  • 1
coincoin
  • 4,595
  • 3
  • 23
  • 47
  • 1
    The version with `!has_to_string::value` produces infinite recursion as `os << t` calls itself. – Jarod42 May 29 '15 at 17:03

1 Answers1

2

You get an ambiguous overload for 'operator<< error when the type is std::string, because the templated version in your code has an equal precedence with the one shipped in the ostream header.

You can check that this is the source of your problem by changing your test program with this:

int main() {
    std::cout << std::string("There is your problem") << std::endl;
}

And you shall still see the same error.

To solve the problem, you can add an explicit definition of operator<< that will take precedence over the two conflicting templates.

std::ostream& operator<<(std::ostream& os, const std::string& t) {
    using std::operator<<;
    os << t;
    return os;
}
Nielk
  • 760
  • 1
  • 6
  • 22
  • Oh thank you. Indeed that worked... Why is there an error then when the type std::string now ? And if possible, can we make a more concise code instead of writing 3 `overloaded operator<<` ? – coincoin May 29 '15 at 16:11
  • @coincoin Edit in my answer to elaborate on that – Nielk May 29 '15 at 16:25
  • @coincoin I don't know any technique to avoid writing another operator<< overload. Strictly speaking, you shall even consider writing overloads for other kinds of strings (std::wstring, etc). – Nielk May 29 '15 at 16:30
  • Thanks. I start to believe that this search for genericity is somehow dangerous since I change the "standard" behaviour... – coincoin May 30 '15 at 08:02