relaxxx
relaxxx

Reputation: 7834

call my template operator<< only if no other found

I want to print state of my objects through toString member function, but I want to call it through String::toString(var). But I want to do this only for object, who doesn't have operator<< defined.

My attempt is below. I have template operator<< which call toString, but I hoped that this operator will be taken into account only if no other suitable operator<< has been found. I was obviously wrong :)

#include <iostream>
#include <sstream>

struct WithToString
{
    std::string toString()
    { return std::string ("foo") ; }
};

struct WithoutToString {};

struct String
{
    template <typename T>
    static std::string toString(T & var)
    {
        std::stringstream s;
        s << var;
        return s.str();
    }
};

template <typename T>
std::ostream & operator<<(std::ostream & s, T & var)
{
    s << var.toString();
    return s;
}

int main(int argc, char *argv[])
{
    int i = 5;
    std::cout << String::toString(i); //fine, prints 5

    WithToString w;
    std::cout << String::toString(w); //fine, prints foo

//    WithoutToString ws;
//    std::cout << String::toString(ws); //fine - give "toString is not a member" error

//    const char * s = "bar";
//    std::cout << String::toString(s); //error - operator << is ambiguous

//    std::string s = "bar";
//    std::cout << String::toString(s); //error - toString is not a member

    return 0;
}

How to achieve this behavior?

EDIT

here is my other attempt, but again fails with string and char *

template <class Type, class V>
class HasOperatorShiftLeft
{
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    template <typename T> struct ToString
    {
        typedef std::ostream & (T::*fptr)(V);
    };

    template <typename T> static Yes HasOpShift(TypeCheck< typename ToString<T>::fptr, &T::operator<< >*);
    template <typename T> static No  HasOpShift(...);

public:
    static bool const value = (sizeof(HasOpShift<Type>(0)) == sizeof(Yes));
};

template <typename T, int A>
struct toStringStr{};

template <typename T>
struct toStringStr<T,1>
{
    static std::string toString(T & var)
    {
        std::stringstream s;
        s << var;
        return s.str();
    }
};

template <typename T>
struct toStringStr<T,0>
{
    static std::string toString(T & var)
    {
        return var.toString();
    }
};

template <typename T>
std::string toString(T & var)
{
    return toStringStr<T,HasOperatorShiftLeft<std::ostream,T>::value>::toString(var);
}

EDIT my newest attempt is posted as Answer, because I think, it works

Upvotes: 1

Views: 191

Answers (2)

Xeo
Xeo

Reputation: 131829

This is actually pretty easy, albeit stupid to do as described in the comments. With C++11:

template<class T>
auto operator<<(std::ostream& os, T const& val)
    -> decltype(os << val.toString())
{
  return os << val.toString();
}

This function will only exist if what's inside decltype(..) is a valid expression. Now just stream everything into an std::ostream& and call it a day. If a type has both toString and and overloaded operator<< for std::ostream&, well, tough. You'll get an "ambiguous call to overloaded operator<<" error.


For C++03, there's another option. Since you seem to kinda dislike free functions, I'll assume you like interfaces. As such, get yourself a Streamable base class with a virtual std::string toString() const = 0 method and overload operator<< only for that. Presto, you have operator<< for all classes that implement that interface!

struct Streamable{
  virtual std::string toString() const = 0;
};

std::ostream& operator<<(std::ostream& os, Streamable const& s){
  return os << s.toString();
}

Or you can even go down to the meta level to get rid of the useless virtual function call:

template<class D>
struct Streamable{
  std::string toString() const{
    return static_cast<D const&>(*this).toString();
  }
};

template<class D>
std::ostream& operator<<(std::ostream& os, Streamable<D> const& s){
  return os << s.toString();
}

// example:
struct Blub
  : public Streamable<Blub>
{
  // implement toString() ...
};

Upvotes: 3

relaxxx
relaxxx

Reputation: 7834

What about this? I think it works just fine...

struct String
{
    template <typename T>
    static std::string toString(const T & var)
    {
        std::stringstream s;
        s << var;
        return s.str();
    }
};

template<typename Elem, typename Traits, typename T>
std::basic_ostream<Elem, Traits> & operator <<(std::basic_ostream<Elem, Traits> & s, const T & var)
{
    s << var.toString();
    return s;
}

Upvotes: 0

Related Questions