以下 has_member 函数无法正常使用 SFINAE 是什么?

What does SFINAE not work correctly with following has_member function?

我正在尝试 Walter Brown's TMP talk 中的示例,并且我正在尝试让他的 has_member 实现正常工作。

然而,该实现似乎错误地 return 正确,这让我相信我不理解 SFINAE 的一些细节。

#include <iostream>
#include <type_traits>

template <class ...>
using void_t = void;

template <class, class = void>
struct has_type_member: std::false_type {};

template <class T> 
struct has_type_member<T, void_t<typename T::type> >: std::true_type {};

struct FooWithType
{
    typedef int type;
};

struct FooNoType 
{
};

int main()
{
    std::cout << "Does FooWithType have type member? " << 
        (has_type_member<FooWithType>() ? "YES" : "NO") << "\n";

    std::cout << "Does FooNoType have type member? " << 
        (has_type_member<FooNoType>() ? "YES" : "NO") << "\n";

    return 1;                                                                                                                 
}      

输出为:

Does FooWithType have type member? YES
Does FooNoType have type member? YES

我在 Ubuntu 上使用 gcc 4.8.2。

问题是 gcc 4.8.2(和 gcc 5.0 之前的版本)不认为别名模板中未使用的参数适用于 SFINAE。解决方法是转发到 voider class 模板:

template <class ... T> struct voider { using type = void; };
template <class ... T>
using void_t = typename voider<T...>::type;

来自 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3911.pdf 第 2.3 节:

Alas, we have encountered implementation divergence (Clang vs. GCC) while working with the above very simple definition. We (continue to) conjecture that this is because of CWG issue 1558: “The treatment of unused arguments in an alias template specialization is not specified by the current wording of 14.5.7 [temp.alias].”