简体   繁体   中英

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, ie 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 GCC 12 or higher while true, true with earlier versions of GCC or any version of clang++.

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;
}

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.

Adding a forwarding declaration help:

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v);

Full code:

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

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v);


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;
}

Demo: https://godbolt.org/z/qKz537TPr

I know I didn't answer "why both version behave differently" but I think it may help:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM