Search code examples
c++templatesc++17template-meta-programmingsfinae

SFINAE code to detect whether operator<< is implemented for a type behaves differently on Clang and gcc


I'm just playing around with some SFINAE code and I can't get it to work. I'm using Xcode 12.2 with -std=c++17. godbolt reveals that Clang does indeed choose the false_type variant no matter what, whereas gcc uses the true_type variant if both template parameters are identical and the operator is implemented, as I would expect it to. See the code below:

#include <iostream>
#include <type_traits>

struct MyStruct {};

std::ostream& operator<<(std::ostream& os, const MyStruct&) {
    return os;
}

template<typename T, typename U = void>
struct has_ostream_operator : std::false_type {};

template<typename T>
struct has_ostream_operator<T, typename std::enable_if_t<sizeof(std::declval<std::ostream&>() << std::declval<T>()), T>> : std::true_type {};

int main() {

    constexpr bool res = has_ostream_operator<MyStruct, MyStruct>::value;
    if constexpr(res) {
        std::cout << "Yes";
    } else {
        std::cout << "No";
    }
    return 0;
}

Can someone explain to me what's going on here and why Clang and gcc would behave differently? I've already solved the problem using a different technique, so this is merely for educational purposes :-)


Solution

  • If you replace the whole sizeof construct with 42, both compilers give you an error about how sizeof isn't a bool and cannot be narrowed to one.

    If you replace it with 1, there is no error (as 1 can obviously be narrowed to true).

    With this in mind, let's pass a bool explicitly:

    template<typename T>
    struct has_ostream_operator<
        T,
        typename std::enable_if_t<
            sizeof(std::declval<std::ostream&>() << std::declval<T>()) != 0,
    //                                                                ^^^^^
            T
        >
    > : std::true_type {};
    

    Now it works.

    Both implementations give me 272 for sizeof(std::declval<std::ostream&>() << std::declval<MyStruct>()), so it's just they're handling this error differently during SFINAEness. I think it's GCC bug 57891.