Reputation: 7834
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
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
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