Printing a textual representation of objects

I am relatively unfamiliar with C ++. Please excuse my terminology if it is incorrect. I tried to find the answer to my own question, but I could not find it (perhaps because I could not formulate my question correctly). I would appreciate it if someone could help me.

I am trying to write a class to create strings that can contain textual representations 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 pass it to the string stream. I think my compiler is ok with this if T

is something like int

or if the class T

defines a method operator std::string()

. However, if T

is some type of type vector<int>

, then it no longer works because it vector<int>

does not define operator std::string()

.

In any case, I could overload this operator so that if T

defines operator std::string()

, I type the text representation, and if not, I'm just typing its address?

Thank.

+3


source to share


2 answers


This can be done by relying on the type trait described here has_insertion_operator

: fooobar.com/questions/471636 / ...

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 get that, the rest is relatively simple:



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 insert statement, print it, otherwise print its address.

This is independent of the conversion operator - std::string

- you only need to make sure your instances are T

"printed" with operator <<

(usually implemented in the same scope as each T

, like namespace or global scope).

+2


source


Here's an example - using some custom traits for the conversion operator for 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;
}

      

+1


source







All Articles