Question

I thought I had figured out how to make a template which determines if a class/struct is stream-able to an ostream (e.g., "print-able") but there is a flaw in the template I wrote to do this. Looking to learn where I went wrong as well as any solutions to get the template to work the way I expect/want.

So my questions are

  1. Am I missing something pretty basic here or is this more complicated than the template takes into account?
  2. I have not looked at the template's interaction with rvalue references: will they be problematic for the template as well?

Full code here.


Here is the template

template<typename T, typename U>
class isOutputStreamable {
  private:
    template<typename V, typename W> static decltype(operator<<(std::declval<V>(), std::declval<W>()), std::true_type()) check_(int);
    template<typename, typename> static std::false_type check_(...);
  public:
    static const bool value = decltype(check_<T, U>(0))::value;
};

The classes I am testing

struct Empty {
};

struct Streamer {
};

std::ostream& operator<<(std::ostream& ostream, const Streamer&) {
  return ostream << "Streamer";
}

The static_asserts that work the way I expect

// Streamer should show as ostreamable
static_assert( isOutputStreamable< std::ostream&, const Streamer& >::value, "Goodness" );
static_assert( isOutputStreamable< std::ostream&, const Streamer  >::value, "Goodness" );
static_assert( isOutputStreamable< std::ostream&,       Streamer& >::value, "Goodness" );
static_assert( isOutputStreamable< std::ostream&,       Streamer  >::value, "Goodness" );

// isOutputStreamable works with a value ostream for Streamer
static_assert( isOutputStreamable< std::ostream, const Streamer& >::value, "Goodness" );
static_assert( isOutputStreamable< std::ostream, const Streamer  >::value, "Goodness" );
static_assert( isOutputStreamable< std::ostream,       Streamer& >::value, "Goodness" );
static_assert( isOutputStreamable< std::ostream,       Streamer  >::value, "Goodness" );

// Empty should not show as ostreamable
static_assert( !isOutputStreamable< std::ostream&, const Empty& >::value, "Goodness" );
static_assert( !isOutputStreamable< std::ostream&, const Empty  >::value, "Goodness" );
static_assert( !isOutputStreamable< std::ostream&,       Empty& >::value, "Goodness" );
static_assert( !isOutputStreamable< std::ostream&,       Empty  >::value, "Goodness" );

// With a const ostream nothing should show as ostreamable
static_assert( !isOutputStreamable< const std::ostream&, const Streamer& >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&, const Streamer  >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&,       Streamer& >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&,       Streamer  >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream , const Streamer& >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream , const Streamer  >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream ,       Streamer& >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream ,       Streamer  >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&, const Empty&    >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&, const Empty     >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&,       Empty&    >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream&,       Empty     >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream , const Empty&    >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream , const Empty     >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream ,       Empty&    >::value, "Goodness" );
static_assert( !isOutputStreamable< const std::ostream ,       Empty     >::value, "Goodness" );

and then the static_asserts that do not

// Unexpected results: I wanted these to all fail
static_assert( isOutputStreamable< std::ostream, const Empty& >::value, "Badness" );
static_assert( isOutputStreamable< std::ostream, const Empty  >::value, "Badness" );
static_assert( isOutputStreamable< std::ostream,       Empty& >::value, "Badness" );
static_assert( isOutputStreamable< std::ostream,       Empty  >::value, "Badness" );

Thanks in advance for any help.


Update

@Casey: Thanks for the help. I was so convinced that there was something wrong with the template I guess I completely missed the fact that it might actually be matching correctly. As for your suggestion about how to switch the template around this is certainly something that is easily do-able; however, I'm curious if there is not another solution that'll fit given what I'll say next.

The template is actually generated from what I had hoped would be a general macro to create these types of checker templates

   #define BINARY_FUNCTION_EXISTS_(FUNCTION_NAME_, CHECK_NAME_) \
    template<typename T, typename U> class CHECK_NAME_ {\
      private:\
        template<typename V, typename W> static decltype(FUNCTION_NAME_(std::declval<V>(), std::declval<W>()), std::true_type()) check_(int);\
        template<typename, typename> static std::false_type check_(...);\
      public:\
        static const bool value = decltype(check_<T, U>(0))::value;\
    };

Is it the case that this ostream-able template needs to be a special case because of the (undesirable in my case) matching template? I think the answer is yes but I've already been confused once on this and it does not hurt to ask. Thanks again.

Was it helpful?

Solution

1. The standard library defines an unconstrained template function that provides rvalue stream insertion for all types in terms of lvalue stream insertion:

C++11 §27.7.3.9 Rvalue stream insertion [ostream.rvalue]

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

1 Effects: os << x

2 Returns: os

This is obviously breaking your detection for rvalue ostreams.

2. Since decltype(std::declval<T>()) and decltype(std::declval<T&&>()) are both T&&, it will work just fine with rvalue references; IsOutputStreamable<T, U&&>::value will be equal to IsOutputStreamable<T, U>::value.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top