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

xml*_*lmx 0 c++ overloading sfinae type-traits c++17

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

第一种情况输出0,表示选择了主模板.所以,我认为第二种情况也应该选择主要模板而不是专用模板; 那么,应该没有错误.

预计Test<void*&>没关系; 让我感到惊讶的是Test<void*>应该不行!

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

Pix*_*ist 5

你的第二个案例是一个很难的错误.

SFINAE @ cppreference.com 说:

只有函数类型或其模板参数类型的直接上下文中的类型和表达式中的失败才是SFINAE错误.如果对替换类型/表达式的评估导致副作用,例如某些模板特化的实例化,隐式定义的成员函数的生成等,那些副作用中的错误被视为硬错误.

第一种情况是正确的,因为remove_pointer如果T是没有效果void*&,那么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;
};
Run Code Online (Sandbox Code Playgroud)