I found this type trait which can be used to check if a certain type T supports operator<<:
template<class Class>
struct has_ostream_operator_impl {
template<class V>
static auto test(V*) -> decltype(std::declval<std::ostream>() << std::declval<V>());
template<typename>
static auto test(...) -> std::false_type;
using type = typename std::is_same<std::ostream&, decltype(test<Class>(0))>::type;
};
template<class Class>
struct has_ostream_operator : has_ostream_operator_impl<Class>::type {};
Source: https://gist.github.com/szymek156/9b1b90fe474277be4641e9ef4666f472
This works fine. Now I'm trying to do the same thing for operator>> using c++11, but I don't get it to work:
template<class Class>
struct has_istream_operator_impl {
template<class V>
static auto test(V*) -> decltype(std::declval<V>() >> std::declval<std::istream>());
template<typename>
static auto test(...) -> std::false_type;
using type = typename std::is_same<std::istream&, decltype(test<Class>(0))>::type;
};
/**
* @brief Type trait to check if operator>>(std::istream, Type) is defined for a given type.
*/
template<class Class>
struct has_istream_operator : has_istream_operator_impl<Class>::type {};
Here is a simplified test for my use case:
#include <sstream>
#include <type_traits>
#include <iostream>
// <include snippet 2>
template<typename T>
typename std::enable_if<has_istream_operator<T>::value, T>::type
fromString(const std::string& str) {
T value;
std::istringstream stream(str);
stream >> value;
return value;
}
int main() {
std::cout << fromString<long>("123") + 1 << std::endl; // expecting 124
return 0;
}
Error is:
has_istream_operator.cpp: In function ‘int main()’:
has_istream_operator.cpp:57:38: error: no matching function for call to ‘fromString<long int>(const char [4])’
std::cout << fromString<long>("123") + 1 << std::endl; // expecting 124
^
has_istream_operator.cpp:49:1: note: candidate: ‘template<class T> typename std::enable_if<has_istream_operator<Class>::value, T>::type fromString(const string&)’
fromString(const std::string& str) {
^~~~~~~~~~
has_istream_operator.cpp:49:1: note: template argument deduction/substitution failed:
has_istream_operator.cpp: In substitution of ‘template<class T> typename std::enable_if<has_istream_operator<Class>::value, T>::type fromString(const string&) [with T = long int]’:
has_istream_operator.cpp:57:38: required from here
has_istream_operator.cpp:49:1: error: no type named ‘type’ in ‘struct std::enable_if<false, long int>’
From which I understand that the SFINAE condition is false and therefore no definition of fromString exists.
What I have tried is to play around with the line
static auto test(V*) -> decltype(std::declval<V>() >> std::declval<std::istream>());
inside my definition of struct has_istream_operator_impl.
This is the variation which makes most sense to me, because when I use the operator>>, I usually do it this way: stream >> value for example and from my (limited) understanding, the test(V*) should test this generically for V:
static auto test(V*) -> decltype(std::declval<std::istream>() >> std::declval<V>());
But it does also not work (same error).
How do get I this to work?
Long story short, you should change
to
There were two errors in the code, due to the following.
>>operator takes the stream as first argument, not as second argument, whereas you are passing the two arguments the other way around.declval<V>()is generating an rvalue (well, not really a value because we are in an unevaluated context), to which you can't assign a value via>>(just like you can'tcin >> 123;), so you have to change it todeclval<V&>().(¹)Update
In reality, prompted by @brahmin's comment, I have to point out another bug in the original code. Follow this reasoning:
has_istream_operator_impl<T>::testis correctly chosen if>>makes sense between anstd::istream(any value category) and aT&(lvalue);has_istream_operator_impl<T>::typewill incorrectly test if the return type of the call totest<T>(0)isstd::istream&.operator>>and they don't all returnstd::istream&:What you want to do, is to have
has_istream_operator_impl<T>::typebestd::true_typeorstd::false_typeonly depending on whetherstd::declval<std::istream>() >> std::declval<V&>()is valid , not on depending on its return type!One typical approach to do so is to use
decltypewith two arguments (or tostatic_cast<void>the result of one-argumentdecltype)So the correct code would be the following
where I've made some cosmetic changes:
has_istream_operatora value rather than a type, so the usage is less verbose:void*rather thanV*for the "positve"testfunction parameter, as that has nothing to do with withV,0tonullptrbecause we are passing it to a function taking a pointer,classtotypenameas using both is confusing or at least distracting, imho.¹ To understand more in depth why that's the case, look at the possible implementation of
std::declvalas shown at the documentation page on cppreference: as you can see, it returns the typetypename std::add_rvalue_reference<T>::type(which, incidentally, can be written asstd::add_rvalue_reference_t<T>since C++14), i.e.std::declval<T>()returnsT&&, which is (by reference collapsing)Ttostd::declval(e.g.std::declval<int&>()),std::declval<int>()).In your usecase you are passing
longas theTtostd::declval, so we are in the second case, i.e.std::declval<long>()returnslong&&. From the page on value categories you can see that an example of xvalue (which is, just like a prvalue, an rvalue) is the following (my emphasis):And that's exactly what
std::declval<long>()is: it has a rvalue reference to object as its return type, hence it returns an rvalue.If you call, instead,
std::declval<T&>()and passlongasT, the call will bestd::declval<long&>(), in which case the return type will belong&, hence the call will return an lvalue. See the following quote, from the same page, of an example of lvalueTo give an example of what
std::declvalis doing, these both passand this fails to compile
where the
// XXXline is whatstd::declval<std::istream>() >> std::declval<V>()is "emulating".