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

Why does SFINAE not work in such a case?

本文关键字:不起作用 这种情况下 SFINAE 为什么      更新时间:2023-10-16
#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,这意味着选择了主模板。因此,我认为第二种情况也应该选择主要模板而不是专用模板;那么,应该没有错误。

意料之中的是,Test<void*&>没事,让我惊讶的是,Test<void*>应该没事!

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

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

SFINAE @ cppreference.com说:

只有函数类型或其模板参数类型的直接上下文中的类型和表达式中的故障才是 SFINAE 错误。如果对替换类型/表达式的计算导致副作用,例如实例化某些模板专用化、生成隐式定义的成员函数等,则这些副作用中的错误将被视为硬错误。

第一种情况是可以的,因为如果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;
};