3

I'm relatively new to C++. Please excuse my terminology if it's incorrect. I tried searching around for an answer to my question, but I could not find it (probably because I couldn't phrase my question correctly). I'd appreciate it if someone could help me.

I am trying to write a class for creating strings that might contain the textual representation of objects or native types. Essentially, I have

private:
    stringstream ss;


public:
    template< typename T >
    Message& operator<<( const T& value ) {
        ss << value;
        return *this;
    }

The overloaded << operator takes some value and tries to stream it into a stringstream. I think my compiler is fine with this if the T is something like int or if the class T defines the method operator std::string(). However, if T is some type like vector<int>, then it no longer works because vector<int> doesn't define operator std::string().

Is there anyway I could perhaps overload this operator so that if T defines operator std::string(), then I print the textual representation, and if it doesn't, I just print its address?

Thanks.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
user2570465
  • 2,437
  • 2
  • 18
  • 22

2 Answers2

2

This can be implemented by building upon the has_insertion_operator type trait described here: https://stackoverflow.com/a/5771273/4323

namespace has_insertion_operator_impl {
typedef char no;
typedef char yes[2];

struct any_t {
  template<typename T> any_t( T const& );
};

no operator<<( std::ostream const&, any_t const& );

yes& test( std::ostream& );
no test( no );

template<typename T>
struct has_insertion_operator {
  static std::ostream &s;
  static T const &t;
  static bool const value = sizeof( test(s << t) ) == sizeof( yes );
};
}

template<typename T>
struct has_insertion_operator :
  has_insertion_operator_impl::has_insertion_operator<T> {
};

Once we have that, the rest is relatively straightforward:

class Message
{
  std::ostringstream ss;

public:
  template< typename T >
  typename std::enable_if<has_insertion_operator<T>::value, Message&>::type
  operator<<( const T& value ) {
    ss << value;
    return *this;
  }

  template< typename T >
  typename std::enable_if<!has_insertion_operator<T>::value, Message&>::type
  operator<<( const T& value ) {
    ss << &value;
    return *this;
  }
};

That is, if there is an insertion operator defined, print the value, otherwise print its address.

This does not rely on a conversion-to-std::string operator being defined--you only need to make sure your T instances are "printable" using operator << (typically implemented in the same scope where each T is defined, e.g. namespace or global scope).

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

Here's an example - using some custom traits for a conversion operator to std::string and the streaming operator:

#include <iostream>
#include <string>

template <class T>
struct traits
{
    template <typename Q>
    static auto hos(Q*) -> decltype(std::declval<const Q>().operator std::string());

    static char hos(...);

    constexpr static bool has_operator_string =
        sizeof hos((T*){0}) != 1;

    // ----

    template <typename Q>
    static auto isab(Q*) -> decltype(std::cout << std::declval<const Q>());

    static char isab(...);


    constexpr static bool is_streamable =
        sizeof isab((T*){0}) != 1;
};

struct S
{
    template <typename T>
    typename std::enable_if<
                 traits<T>::has_operator_string,
                 S&>::type
    operator<<(const T& value)
    {
        std::cout << "string() " << value.operator std::string() << '\n';
        return *this;
    }    

    template <typename T>
    typename std::enable_if<!traits<T>::has_operator_string && traits<T>::is_streamable, S&>::type
    operator<<(const T& value)
    {
        std::cout << "<< " << value << std::endl;
        return *this;
    }

    template <typename T>
    typename std::enable_if<
                 !traits<T>::has_operator_string &&
                 !traits<T>::is_streamable,
                 S&>::type
    operator<<(const T& value)
    {
        std::cout << "T& @" << &value << std::endl;
        return *this;
    }
};

struct X
{
    operator std::string() const { return "hi"; }
};

struct Y
{

};

int main()
{
    std::cout << "> main()" << std::endl;

    std::cout << "X() ";
    S() << X();

    Y y;
    std::cout << "Y y; ";
    S() << y;

    std::cout << "Y() ";
    S() << Y();

    std::cout << "\"text\" ";
    S() << "text";
    std::cout << "< main()" << std::endl;
}
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252