为什么 SFINAE 在这种情况下不起作用?

Why does SFINAE not work in such a case?

#include <iostream>
#include <type_traits>

template<typename T>
struct A
{
    using m = std::remove_pointer_t<T>&;
};

template
<
    typename T,
    typename = std::void_t<>
>
struct Test
{
    enum { value = 0 };
};

template<typename T>
struct Test<T, typename A<T>::m>
{
    enum { value = 1 };
};

int main()
{
    std::cout << Test<void*&>::value; // ok, output 0
    std::cout << Test<void*>::value; // error : cannot form a reference to 'void'
}

第一种情况输出0,这意味着主模板是selected。所以,我认为第二种情况也应该 select 主要模板而不是专用模板;那么应该不会报错。

预计Test<void*&>可以;让我意外的是Test<void*>应该不行吧!

为什么 SFINAE 在后一种情况下不起作用?

你的第二种情况是硬错误。

SFINAE @ cppreference.com 说:

Only the failures in the types and expressions in the immediate context of the function type or its template parameter types are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors.

第一种情况是可以的,因为如果 Tvoid*&remove_pointer 没有效果,那么 mvoid*& 因为引用崩溃和对指向 void 的指针的引用是有效类型,而对 void 的引用则不是。

第一种情况下的类型仍然是Test<void*&, void>而不是Test<void*&, void*&>,因为您只指定了第一个模板参数。

第二种情况失败而不是第一种情况的原因是编译器必须实例化专用模板,因为第二个参数是非推导上下文,因此编译器无法立即判断专用化是否更好比赛。但在第二种情况下,实例化会产生硬错误,而在第一种情况下则不会。

仍选择主模板,因为它更匹配(同时仍将实例化专用模板以检查它是否匹配)。

注意:我不能说特化是否实际上已完全实例化,或者编译器是否只是查找 typename A<T>::m 以检查此特化是否更匹配。然而结果是一样的。如果 void* 存在硬错误。

另请注意,无论如何使用 C++17 时,人们可能更愿意使用 constexpr 成员而不是枚举。

template<typename T>
struct Test<T, typename A<T>::m>
{
    static constexpr unsigned value = 1u;
};