SFINAE,演绎与实例化

SFINAE, deduction vs. instantiation

本文关键字:实例化 演绎 SFINAE      更新时间:2023-10-16

这段代码在大多数编译器中都无法编译,但一开始我直觉上希望SFINAE能保护我:

typedef void (*A)();
template < typename T >
struct a_metafun { typedef typename T::type type; };
template < typename T >
typename a_metafun<T>::type f(T) {}
template < typename T>
void f(T(*)()) {}
int main() { f(A()); }

我可以用至少两种方法来解决这个问题:

  1. 更改";metafun";f((到:

    template < typename T > typename T::type f(T) {}

  2. 定义a_metafun,使其分析T,并且如果T有类型则具有类型,如果没有则不具有类型。。。但无论哪种方式都可以无错误地实例化:

    BOOST_MPL_HAS_XXX_TRAIT_DEF(type)
    typedef < template T, bool = has_type<T>::value >
    struct a_metafun { };
    typedef < template T >
    struct a_metafun<T, true> { typedef typename T::type type };
    

在看14.8.2(C++03(时,我觉得它确切地规定了SFINAE可以在什么条件下应用。有更好的地方看吗?一个已经推导出的模板的实例化中的失败,即使在另一个模板的推导过程中,似乎也不包括在这个列表中。

我解释这是非法的另一个方向是,a_metafun的推导已经发生,其内部的实例化是导致错误的原因。SFINAE不适用于实例化,而只适用于推导,或者我错了吗?然而,在第二种情况下,a_metafun被正确地、形式良好地实例化,但它只是没有";类型";中的定义,这意味着试图实例化它的模板由于替换而失败。

基本上,我想知道标准中有什么规定了我所目睹的行为。我试过的每一个编译器都会抱怨,甚至是comeau。我认为他们这样做是正确的,只是我不完全确定为什么。

所以,专家们。。。什么?为什么即使在f((中的推导上下文中,类型的实例化也会导致错误,而不是SFINAE排除?

在C++03规范中,SFINAE的规则有点模糊,允许编译器作者转到任何长度来查找替换失败,从而导致SFINAE。C++03中的相关文本§14.8.2/2说,

-[…]如果模板参数或函数模板的函数类型中的替换导致类型无效,则类型推导失败[…]

它进一步解释了失败的几个原因,但没有一个真正说明在什么时候替换失败应该被视为SFINAE。所以我想,你的代码在C++03中可能工作得很好(也可能不工作,这取决于编译器作者如何解释文本。无论如何,这让我很困惑(。

但是C++11中的单词已经得到了改进,消除了模糊性。它在§14.8.2/8中说,

如果替换导致类型或表达式无效,则类型推导将失败。无效的类型或表达式是指如果使用替换的参数编写,则会出现格式错误的类型或表达。[注意:访问检查是作为替换过程的一部分进行的。--结束注释]只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致推导失败。

术语"即时上下文">很有趣,我认为它适用于您的情况。更具体地说,元函数a_metafun中的替换失败不被视为函数类型的"即时上下文">。它在C++11中是错误的,而不是SFINAE。

但是,尽管C++11引入了短语"即时上下文"来使文本稍微好一点,但该短语的定义仍然不够清晰。这里有一个活跃的问题:

  • 1844.定义"直接上下文">

SFINAE不会保护您,错误发生在类型推导之后。然而,这应该有效:

template < typename T, typename Type = typename T::type >
struct a_metafun { typedef Type type; };

通过在默认模板参数中访问T::type,我们会在替换时发生这种情况,此时SFINAE开始生效。

编辑:经过进一步思考,我不确定您当前的实现失败的确切原因。我认为这是因为a_metafun有一个成员类型type,它会导致编译错误;如果CCD_ 10根本不具有成员类型CCD_。