I want to test whether a class is streamable to ostream&
by seeing whether an overload for operator<<
is provided. Based on these posts, I tried to write another version using C++11. This is my attempt:
#include <iostream>
#include <type_traits>
namespace TEST{
class NotDefined{};
template<typename T>
NotDefined& operator << (::std::ostream&, const T&);
template <typename T>
struct StreamInsertionExists {
static std::ostream &s;
static T const &t;
enum { value = std::is_same<decltype(s << t), NotDefined>() };
};
}
struct A{
int val;
friend ::std::ostream& operator<<(::std::ostream&, const A&);
};
::std::ostream& operator<<(::std::ostream& os, const A& a)
{
os << a.val;
return os;
}
struct B{};
int main() {
std::cout << TEST::StreamInsertionExists<A>::value << std::endl;
std::cout << TEST::StreamInsertionExists<B>::value << std::endl;
}
But this fails to compile:
test_oper.cpp:40:57: error: reference to overloaded function could not be resolved; did you mean to call it? std::cout << TEST::StreamInsertionExists<A>::value << std::endl; /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ostream:1020:1: note: possible target for call endl(basic_ostream<_CharT, _Traits>& __os) test_oper.cpp:30:17: note: candidate function not viable: no known conversion from 'TEST::NotDefined' to '::std::ostream &' (aka 'basic_ostream<char> &') for 1st argument ::std::ostream& operator<<(::std::ostream& os, const A& a) test_oper.cpp:15:15: note: candidate template ignored: couldn't infer template argument 'T' NotDefined& operator << (::std::ostream&, const T&);
However, if I replace the line
enum { value = std::is_same<decltype(s << t), NotDefined>() };
with
static const bool value = std::is_same<decltype(s << t), NotDefined>();
then everything compiles.
Why is there such a difference between the enum
and the bool
?
value
is an enum of anonymous name in StreamInsertionExists<T>
. When you try to do:
std::cout << StreamInsertionExists<T>::value;
The compiler is doing overload lookup on operator<<(std::ostream&, StreamInsertionExists<T>::E)
. In the typical case, it'd do integral promotion on the enum
and stream it as int
. However, you additionally defined this operator:
template<typename T>
NotDefined& operator << (std::ostream&, const T&);
That is a better match for the enum
than the int
version (Exact Match vs integral promotion), so it is preferred. Yes, it's a function template, but non-templates are only preferred if the conversion sequences match - and in this case they don't.
Thus, this line:
std::cout << TEST::StreamInsertionExists<A>::value << std::endl;
which is:
operator<<(operator<<(std::cout, TEST::StreamInsertionExists<A>::value), std::endl);
And the inner most operator<<
call will use your NotDefined&
template. Thus, the next step would be to find an appropriate function call for:
operator<<(NotDefined&, std::endl);
and there is no such overload for operator<<
hence the error.
If you change value
to be bool
, there is no such problem because there is an operator<<
that takes bool
exactly: #6. That said, even with bool
, your trait is still incorrect as it always returns false
. Your NotDefined
version actually returns a reference, so you'd have to check against that. And also, NotDefined&
means not defined, so you'd have to flip the sign:
static const bool value = !std::is_same<decltype(s << t), NotDefined&>();
However, this is particularly error prone. Now cout << B{};
instead of failing to compile would instead give you a linker error, and cout << B{} << endl;
gives you the same confusing overload error involving endl
instead of simply statying that you can't stream a B
.
You should prefer to just do:
template <typename...>
using void_t = void;
template <typename T, typename = void>
struct stream_insertion_exists : std::false_type { };
template <typename T>
struct stream_insertion_exists<T, void_t<
decltype(std::declval<std::ostream&>() << std::declval<T>())
> > : std::true_type { };