57

http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx,VC++团队正式声明他们还没有实现 C++11 的核心特性“Expression SFINAE”。但是, VC++ 编译器接受从http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html复制的以下代码示例。

示例 1:

template <int I> struct A {};

char xxx(int);
char xxx(float);

template <class T> A<sizeof(xxx((T)0))> f(T){}

int main()
{
    f(1);
}

示例 2:

struct X {};
struct Y 
{
    Y(X){}
};

template <class T> auto f(T t1, T t2) -> decltype(t1 + t2); // #1
X f(Y, Y);  // #2

X x1, x2;
X x3 = f(x1, x2);  // deduction fails on #1 (cannot add X+X), calls #2

我的问题是:什么是“表达 SFINAE”?

4

1 回答 1

72

我认为,您链接的论文中对表达式 SFINAE 进行了很好的解释。这是关于表达式的SFINAE 。如果里面的表达式decltype无效,那么,从重载的 VIP 休息室踢掉这个函数。您可以在此答案的末尾找到规范性措辞。

关于 VC++ 的注释:他们没有完全实现。在简单的表达式上,它可能有效,但在其他表达式上,它不会。有关失败的示例,请参阅此答案的评论中的讨论。为简单起见,这是行不通的:

#include <iostream>

// catch-all case
void test(...)
{
  std::cout << "Couldn't call\n";
}

// catch when C is a reference-to-class type and F is a member function pointer
template<class C, class F>
auto test(C c, F f) -> decltype((c.*f)(), void()) // 'C' is reference type
{
  std::cout << "Could call on reference\n";
}

// catch when C is a pointer-to-class type and F is a member function pointer
template<class C, class F>
auto test(C c, F f) -> decltype((c->*f)(), void()) // 'C' is pointer type
{
  std::cout << "Could call on pointer\n";
}

struct X{
  void f(){}
};

int main(){
  X x;
  test(x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

使用 Clang,这会输出预期的结果:

可以用引用
调用 可以用指针
调用 不能调用

使用 MSVC,我得到......好吧,编译器错误:

1>src\main.cpp(20): error C2995: ''unknown-type' test(C,F)' : 函数模板已经定义
1> src\main.cpp(11) : 见“测试”声明

GCC 4.7.1 似乎也不能胜任这项任务:

source.cpp: 代替 'template decltype ((c.*f(), void())) test(C, F) [with C = X*; F = 无效 (X::*)()]':
source.cpp:29:17: 从这里需要
source.cpp:11:6: 错误:不能将成员指针“f”应用到“c”,它是非类类型“X*”
source.cpp: 代替 'template decltype ((c.*f(), void())) test(C, F) [with C = int; F = int]':
source.cpp:30:16:从这里需要
source.cpp:11:6:错误:“f”不能用作成员指针,因为它是“int”类型

表达式 SFINAE 的一个常见用途是在定义特征时,例如检查一个类是否具有某个成员函数的特征:

struct has_member_begin_test{
  template<class U>
  static auto test(U* p) -> decltype(p->begin(), std::true_type());
  template<class>
  static auto test(...) -> std::false_type;
};

template<class T>
struct has_member_begin
  : decltype(has_member_begin_test::test<T>(0)) {};

活生生的例子。(令人惊讶的是,它在 GCC 4.7.1 上再次起作用。)

另请参阅我的这个答案,它在另一个环境中使用相同的技术(也就是没有特征)。


规范性措辞:

§14.8.2 [temp.deduct]

p6在模板实参推导过程中的某些点,有必要采用一个使用模板形参的函数类型,并将这些模板形参替换为相应的模板实参。当任何显式指定的模板参数被替换为函数类型时,这在模板参数推导的开头完成,并且在模板参数推导结束时,当任何从默认参数推导或获得的模板参数被替换时再次完成。

p7替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。表达式不仅包括常量表达式,例如出现在数组边界或作为非类型模板参数的常量表达式,还包括、和其他允许非常量表达式的上下文中的通用表达式(即非常量表达式) 。 sizeofdecltype

p8 如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是如果使用替换参数编写的格式错误的类型或表达式。[...]

于 2012-09-29T16:10:34.210 回答