#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 在后一种情况下不起作用?
最佳答案
你的第二种情况是一个硬错误。
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.
第一种情况没问题因为remove_pointer
如果 T
则无效是void*&
, 然后 m
是void*&
由于引用崩溃,指向 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;
};
关于c++ - 为什么 SFINAE 在这种情况下不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43017759/