I am trying to define a has_ostream_operator<T>
SFINAE test for checking whether I can cout a given type. I have it working, but only if in my definition of has_ostream_operator
I call operator<<
as a method rather than as an infix operator. In other words this works:
decltype(std::declval<std::ostream>().operator<<(std::declval<T>()))>
This does not:
decltype(std::declval<std::ostream>() << std::declval<T>())>
Test case below (can also see at http://coliru.stacked-crooked.com/a/d257d9d6e0f3f6d9). Note that I included a definition of void_t since I'm only on C++14.
#include <iostream>
namespace std {
template<class...>
using void_t = void;
}
template<class, class = std::void_t<>>
struct has_ostream_operator : std::false_type {};
template<class T>
struct has_ostream_operator<
T,
std::void_t<
decltype(
std::declval<std::ostream>().operator<<(std::declval<T>()))>>
: std::true_type {};
struct Foo {};
template<class X>
void print(
const X& x,
std::enable_if_t<has_ostream_operator<X>::value>* = 0)
{
std::cout << x;
}
template<class X>
void print(
const X&,
std::enable_if_t<!has_ostream_operator<X>::value>* = 0)
{
std::cout << "(no ostream operator<< implementation)";
}
int main()
{
print(3); // works fine
print(Foo()); // this errors when using infix operator version
return 0;
}
I'm assuming your "infix" version used this expression:
std::declval<std::ostream>() << std::declval<T>()
The reason that matches for Foo
is because the first part, declval<ostream>()
produces an rvalue of type ostream&&
. This matches a non-member operator<<
:
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
That overload simply forwards the call along:
Calls the appropriate insertion operator, given an rvalue reference to an output stream object (equivalent to
os << value
).
You should instead check for that directly. All the overloads take an ostream
by lvalue reference, so you should test that too:
std::declval<std::ostream&>() << std::declval<T>()
You need
std::declval<std::ostream&>() << std::declval<T>()
// ^
std::declval<std::ostream>()
is an rvalue; you are hitting the catch-all operator<<
overload for rvalue streams.
If you use infix notation, the rvalue stream inserter is found, since declval
returns rvalues per se; [ostream.rvalue]:
template <class charT, class traits, class T>
basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>&& os, const T& x);
This overload currently accepts all arguments for x
. I've submitted LWG #2534, which, if resolved accordingly, will make your initial code work as expected.
A temporary workaround is to make declval
return an lvalue reference, i.e. adjust the template argument to one:
std::declval<std::ostream&>() << std::declval<T>()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With