为什么 SFINAE 在 gcc <11 和 >12 上有不同的行为?

Arm*_*mut 7 c++ gcc clang sfinae language-lawyer

我在这里看到了使用 SFINAE 检查类型是否可流式传输的示例。但是,我注意到它不可移植,即使用不同的编译器为模板化类型返回不同的结果。我很高兴能提供任何帮助理解这里问题的提示。

下面的代码返回true, false任何版本的 clang++ 和 GCC 12 或更高版本,但true, true使用早期版本的 GCC。

您可以在这里在线尝试。

#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;
}
Run Code Online (Sandbox Code Playgroud)

Jar*_*d42 6

operator<<(std::ostream& os, const std::vector<T>& v)ADL 不会找到(对于std::vector<int>,它会被发现std::vector<C>)(因此需要在使用之前声明才能使用)。

这就是为什么正确答案是truefalse。以前版本的 gcc 在这方面行为不当。

注意:不鼓励为您不拥有的类型重载运算符。std 将来可能会添加该重载,并且您将违反 ODR(一个定义规则)。如果另一个图书馆做了与你同样的错误事情,情况也是如此。