Search code examples
c++c++11sfinaeostreamg++4.9

Traits to detect if a class is compatible with std::ostream<<


I tried to implement a type_traits able to detect if a class can be used in the context of an instruction such as: std::cout << my_class_instance;.

My implementation tried to benefit from SFINAE to detect whether or not the function std::ostream& operator<<(std::ostream&, const MyClass&) is available for the class. Unfortunately it fails under g++-4.9 which should be compatible with C++ 11. Other compilers does not complain and seem to generate the right code: g++-5+, clang++-3.3+ and also Visual Studio.

Here is the implementation I tried so far:

#include <iostream>
#include <type_traits>
#include <vector>

template <class... Ts> using void_t = void;
template <class T, class = void> struct can_be_printed : std::false_type {};
template <class T> struct can_be_printed<T, void_t<decltype(std::cout << std::declval<T>())>> : std::true_type {};

static_assert(!can_be_printed<std::vector<int>>::value, "vector<int> cannot be printed");
static_assert(can_be_printed<int>::value, "int can be printed");

Live example is available at: https://godbolt.org/g/6xFSef . Please do not hesitate if you need more details.


Solution

  • This is an issue with how gcc interprets:

    template <class... Ts> using void_t = void;
    

    before it handled the core language issue that specifically affected that (1558). Basically, gcc sees that this alias isn't affected by the template parameters and just drops in void. That completely defeats the purpose of void_t if it always succeeds, which is what is happening in this case.

    You can fool it by just wrapping void_t in another template (as suggested in the original paper):

    template <class... Ts> struct make_void { using type = void; };            
    template <class... Ts> using void_t = typename make_void<Ts...>::type;
    

    Here, gcc can't just drop the substituation, since there is of course hypothetically some other make_void specialization that isn't just void.