Search code examples
c++gccclanglanguage-lawyersfinae

Why SFINAE has different behavior with gcc <11 vs >12?


I saw this example of using SFINAE to check if a type is streamable here. However, I noticed that it is not portable, i.e. returns different results for templated types with different compilers. I'd be glad for any tips to understand the problem here.

The code below returns true, false with any version of clang++ and GCC 12 or higher, but true, true with earlier versions of GCC.

You can try it online here.

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

template <typename T, typename dummy = void>
struct is_printable : std::false_type {};

template <typename T>
struct is_printable<
    T, typename std::enable_if_t<std::is_same_v<
           std::remove_reference_t<decltype(std::cout << std::declval<T>())>,
           std::ostream>>> : std::true_type {};

template <typename T>
inline constexpr bool is_printable_v = is_printable<T>::value;

struct C {};
std::ostream& operator<<(std::ostream& os, const C& c) {
    os << "C";
    return os;
}

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
    for (const auto& el : v) {
        os << el;
    }
    return os;
}

int main(int argc, const char* argv[]) {
    std::cout << std::boolalpha;
    std::cout << is_printable_v<C> << std::endl;
    std::cout << is_printable_v<std::vector<int>> << std::endl;
    return 0;
}

Solution

  • operator<<(std::ostream& os, const std::vector<T>& v) won't be found by ADL (for std::vector<int>, it would for std::vector<C>) (and so need to be declared before usage to be usable).

    That is why correct answer is true, false. previous version of gcc misbehave on this.

    Note: It is discouraged to overload operator for types which you don't own. std might in the future add that overload, and you will have ODR (One definition rule) violation. Same if another library does the same wrong thing than you.