3

I need to check if a given class has the <<(cls, ostream) operator defined or not. If so, I want my function to use that to write to ostringstream, otherwise boilerplate code should be used.

I know this question has been asked before. However, I generally find custom solutions that don't always work on my compiler (clang++). After many hours of searching, I finally found out that boost::type_traits. I hadn't look there previously, because I had assumed c++11 already copied everything in the traits department that boost had.

The solution that worked for me was to do:

template <typename C>
std::string toString(C &instance) {
    std::ostringstream out;
    out << to_string<C, boost::has_left_shift<C, std::ostream>::value>::convert(ctx);
    return out.str();
}

with to_string defined as:

template <typename C, bool>
struct to_string {
    // will never get called
    static std::string convert(LuaContext &ctx) {}
};

template <typename C>
struct to_string<C, true> {
    static std::string convert(LuaContext &ctx) {
        return "convert(true) called.";
    }
};

template <typename C>
struct to_string<C, false> {
    static std::string convert(LuaContext &ctx) {
        return "convert(false) called.";
    }
};

So I'm posting this for two reasons:

  1. Check if this is the sanest method to use, or see if someone else can suggest an even better solution (i.e. the question is more out of curiosity of approach than "will this work?" -- it already works for me)

  2. Post this up to save someone else hours of searching in case she/he also needs to do something similar.

  3. As a more general question -- sometimes trait classes appear to return std::true_type or std::false_type (well, at least for non-boost classes). Other times they are bools. Is there a reason for this discrepancy? If boost:has_left_shift returned a type instead of a bool, then I could have just a single to_string struct.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Abe Schneider
  • 977
  • 1
  • 11
  • 23

1 Answers1

8

Quick-and-dirty C++11 SFINAE:

template<typename T,
         typename = decltype(
           std::declval<std::ostream&>() << std::declval<T const&>()
         )
>
std::string toString(T const& t)
{
    std::ostringstream out;
    // Beware of no error checking here
    out << t;
    return out.str();
}

template<typename T,
         typename... Ignored
>
std::string toString(T const& t, Ignored const&..., ...)
{
    static_assert( sizeof...(Ignored) == 0
                 , "Incorrect usage: only one parameter allowed" );
    /* handle any which way here */
}

If you want you can also check that the return type of stream << val is indeed convertible to std::ostream&:

template<typename T,
         typename Result = decltype(
           std::declval<std::ostream&>() << std::declval<T const&>()
         ),
         typename std::enable_if<
             std::is_convertible<Result, std::ostream&>::value,
             int
         >::type = 0
>

As for a not so quick-and-dirty solution I'd introduce an is_stream_insertable trait, which implementation can make use of the very same tricks used here.

Be aware that std::integral_constant<bool, B> has a conversion operator to bool, this might explain some of the things you have observed. I also do not recommend mixing the C++11 Standard types and traits with Boost: don't mix up std::true_type with boost::true_type! Which is not to say that you shouldn't use e.g. Boost.TypeTraits at all with C++11, but try to be consistent and only use one of two at a time.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114