2017-03-25 85 views
0
#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,这意味着主模板被选中。所以,我认为第二种情况也应该选择主模板而不是专用模板;那么,不应该有错误。为什么SFINAE在这种情况下不起作用?

预计Test<void*&>没问题;令我惊讶的是Test<void*>应该不行!

为什么SFINAE不适用于后一种情况?

+0

可能是这种情况,因为'A :: m'是一个不可推论的上下文。 – Pixelchemist

+0

如果是这样,为什么前一种情况好吗? – xmllmx

+0

由于参考折叠和'remove_pointer'没有效果。 – Pixelchemist

回答

5

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

SFINAE @ cppreference.com说:

只有在类型和表达式的函数类型或它的模板参数类型的直接背景失败是SFINAE错误。如果替代类型/表达式的评估会导致一些副作用,例如某些模板特化的实例化,隐式定义的成员函数的生成等,则这些副作用中的错误将被视为硬错误。

第一种情况是可以的,因为remove_pointer没有效果,如果是Tvoid*&,然后mvoid*&因为参考塌陷和指针的引用中的孔隙是一个有效的类型,而到一个无效引用不是的。

第一种情况下的类型仍然是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; 
}; 
+0

第一种情况输出'0',这意味着重载类是主模板而不是专用类。 – xmllmx

+0

如果您的陈述是真实的,那么第一个案例将输出'1',因为它使用专门的模板。 – xmllmx

+0

如果第一种情况输出“0”,则第二种情况也应输出“0”。这正是我想知道的。 – xmllmx

相关问题