I use the C ++ 03 method to detect the presence of a function at compile time . I should use this method, not the void_t method, although I use C ++ 14 because I need to support GCC 4.9, and these are errors when using the void_t method (oddly enough, only Ubuntu 14 GCC 4.9 has this problem, not version of Fedora, but it is fixed all over the board in GCC5 + AFAICT).
In particular, I check for the presence of operator<<(std::ostream&, const T&) so that I have a beautiful print function that accepts any type. When a function is called, you get regular ostream output if the type supports it, and you get a message about the lack of implementation when the operator is not defined. The code is below.
This worked for me so far until I came across a type defined by a third-party library that I cannot change. The type has implicit conversion operators for both bool and float. This means that if the SFINAE check is performed to check if s << t valid, I get a compiler error because s << t ambiguous. In this case, I would prefer it to simply report that there is no such implementation as usual, instead of trying to select an implicit conversion. Is there a way to modify the SFINAE check to make this possible? I checked and the void_t method with GCC5 seems to do what I want (commented out in the code below), but I cannot use it for the reasons stated above.
Test case:
#include <iostream> #include <typeinfo> #include <type_traits> namespace detail { namespace has_ostream_operator_impl { typedef char no; typedef char yes[2]; struct any_t { template<typename T> any_t( T const& ); }; no operator<<( std::ostream const&, any_t const& ); yes& test( std::ostream& ); no test( no ); template<typename T> struct has_ostream_operator { static std::ostream &s; static T const &t; // compiler complains that test(s << t) is ambiguous // for Foo static bool const value = sizeof( test(s << T(t)) ) == sizeof( yes ); }; } template<typename T> struct has_ostream_operator : has_ostream_operator_impl::has_ostream_operator<T> { }; // 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&>() << std::declval<const T&>())>> // : std::true_type {}; } template<class X> std::enable_if_t< detail::has_ostream_operator<X>::value && !std::is_pointer<X>::value> prettyPrint(std::ostream& o, const X& x) { o << x; } template<class X> std::enable_if_t< !detail::has_ostream_operator<X>::value && !std::is_pointer<X>::value> prettyPrint(std::ostream& o, const X& x) { o << typeid(x).name() << " (no ostream operator<< implementation)"; } template<class X> void prettyPrint(std::ostream& o, const X* x) { o << "*{"; if(x) { prettyPrint(o, *x); } else { o << "NULL"; } o << "}"; } struct Foo { operator float() const { return 0; } operator bool() const { return false; } }; struct Bar {}; int main() { Bar x; Foo y; prettyPrint(std::cout, 6); // works fine std::cout << std::endl; prettyPrint(std::cout, Bar()); // works fine std::cout << std::endl; prettyPrint(std::cout, x); // works fine std::cout << std::endl; prettyPrint(std::cout, &x); // works fine std::cout << std::endl; // prettyPrint(std::cout, y); // compiler error std::cout << std::endl; return 0; }
source share