SFINAE,演绎与实例化

时间:2012-12-31 06:10:41

标签: c++ standards

此代码无法在大多数编译器中编译,但起初我直观地期望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正确,并且格式良好地实例化,但它内部没有“类型”定义,这意味着试图实例化它的模板由于替换而失败。

基本上我想知道标准中的内容是指定我正在目击的行为。我试过的每个编译器都会抱怨,甚至是冒险。我认为这样做是正确的,我只是不确定为什么。

所以,专家......什么是什么?为什么类型的实例化,即使在f()中的演绎语境中导致错误而不是SFINAE排除?

2 个答案:

答案 0 :(得分:4)

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

  

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

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

但是C ++ 11中的措辞已被改进,消除了模糊性。它在§14.8.2/ 8中说,

  

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

术语“直接上下文”很有意思,我认为它适用于您的情况。更具体地说,元函数a_metafun中的替换失败不被认为是函数类型的“立即上下文”。它在C ++ 11中是不正确的,而不是SFINAE。

但是,尽管C ++ 11引入了“立即上下文”这一短语来使文本稍好一些,但这个短语的定义还不够明确。这是一个活跃的问题:

答案 1 :(得分:2)

SFINAE 不会保护您,类型扣除后发生错误。但是,这应该有效:

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

通过在默认模板参数中访问T::type,我们会在替换时将其发生,并且 SFINAE 会在此时启动。

编辑:在考虑了一些之后,我不确定您当前的实施失败的原因。我认为是因为a_metafun 成员类型type,导致编译错误;如果a_metafun根本没有成员类型type,情况会有所不同。