pisiiki
pisiiki

Reputation: 121

Check for existence of global operator<< in C++

Hello I want to write two implementations of the to_string member function as follows:

template <typename T0> class foo
{
public: std::string     to_string();
public: T0              m_Value;
};

template <typename T0> std::string foo<T0>::to_string()
{
std::stringstream ss;
ss << m_Value;
return ss.str();
}

template <typename T0> std::string foo<T0>::to_string()
{
return typeid(T0).name();
}

I have seen this, however I don't know how to use the code, I'm not used to enable_if and boost mpl at all. How should I define the two to_string functions to use the second as a fallback?

Thanks.

Upvotes: 2

Views: 754

Answers (3)

nikolas
nikolas

Reputation: 8975

My take on this: you can take the metafunction you found as is, it works nicely. Let's still discuss briefly why it is working:

sizeof does not actually evaluate an expression; it deduces its type and returns the size of that type. Type sizes are implementation defined and we cannot assume much about them, but we know that sizeof(char) != sizeof(char[2]), so we use these types to test for.

We define a stream operator at namespace level using an any_t type, that will accept - you guessed it - any type and let it return something (it's not actually important what type, as long as it's not ostream &). This is what we fall back to if the type does not have a stream operator defined. In the class itself we now define two functions, one taking an ostream &, which will be the result if the stream operator is defined, and one taking the return type we defined for our fallback function.

We can now test sizeof(test(s << c)) which, again, will not evaluate the expression, only determine the return type and return its size.

Now all that we understand how that works, all there's left to do is to embed this into our application. There are several approaches to do this; one way, which also works prior C++11 is to use a functor:

template <bool, typename T>
struct to_string_functor
{
    std::string operator()(T const & t) const
    {
        std::stringstream ss;
        ss << t;
        return ss.str();
    }
};

template <typename T>
struct to_string_functor<false, T>
{
    std::string operator()(T const &) const
    {
        return typeid(T).name();
    }
};

template <typename T>
struct foo
{
    std::string to_string() const
    {
        return to_string_functor<
            has_insertion_operator<T>::value, T
        >()(m_Value);
    }
    /* ... */
};

There are more ways to do this, another one being enable_if, if C++11 is available for you (you will probably want partially specialized functions to do this); you may want to read this excellent blog post on this matter.

In this simple case however, a functor should do in my opinion.

Upvotes: 3

Viktor Sehr
Viktor Sehr

Reputation: 13099

You could use boost::has_left_shift

// From the documentation

#include <boost/type_traits/has_left_shift.hpp>
#include <iostream>

template <class T>
struct contains { T data; };

template <class T>
bool operator<<(const contains<T> &lhs, const contains<T> &rhs) {
    return f(lhs.data, rhs.data);
}

class bad { };
class good { };
bool f(const good&, const good&) { }

int main() {
    std::cout<<std::boolalpha;
    // works fine for contains<good>
    std::cout<<boost::has_left_shift< contains< good > >::value<<'\n'; // true
    contains<good> g;
    g<<g; // ok
    // does not work for contains<bad>
    std::cout<<boost::has_left_shift< contains< bad > >::value<<'\n'; // true, should be false
    contains<bad> b;
    b<<b; // compile time error
    return 0;
}

Upvotes: 2

user2249683
user2249683

Reputation:

#include <iostream>
#include <sstream>
#include <typeinfo>

// has_insertion_operator
// ============================================================================

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> {
};


// ToString
// ============================================================================

namespace Detail {
    template <typename T, bool>
    struct ToString {
        static std::string apply(const T& value)
        {
            std::stringstream s;
            s << value;
            return s.str();
        }
    };

    template <typename T>
    struct ToString<T, false> {
        static std::string apply(const T& value)
        {
            return typeid(T).name();
        }
    };
}

template <typename T>
inline std::string to_string(const T& value)
{
    return Detail::ToString<T, has_insertion_operator<T>::value>::apply(value);
}

has_insertion_operator was copied from the linked answer by Paul J. Lucas in (Using SFINAE to check for global operator<<?).

You could also use the inline friend solution of Mike Seymour shown in (How to convert anything to string implicitly?). I prefer the SFINAE though.

Upvotes: 2

Related Questions