What is "Expression SFINAE"?
Asked Answered
K

1

58

At http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx, the VC++ team officially declare that they have not yet implemented the C++11 core feature "Expression SFINAE". However, The following code examples copied from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html are accepted by the VC++ compiler.

example 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);
}

example 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

My question is: What is "Expression SFINAE"?

Kenti answered 29/9, 2012 at 15:37 Comment(3)
Why do not look in the obvious: en.wikipedia.org/wiki/Substitution_failure_is_not_an_errorRighthand
@SChepurin: That explains normal SFINAE, not Expression SFINAE. It's a good point, though. Does OP know about SFINAE in general?Xenomorphic
@Xeo, Yes, I know SFINAE in general. Many thanks to you for your illustrative explanation.Kenti
X
72

Expression SFINAE is explained quite well in the paper you linked, I think. It's SFINAE on expressions. If the expression inside decltype isn't valid, well, kick the function from the VIP lounge of overloads. You can find the normative wording at the end of this answer.

A note on VC++: They didn't implement it completely. On simple expressions, it might work, but on others, it won't. See a discussion in the comments on this answer for examples that fail. To make it simple, this won't work:

#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);
}

With Clang, this outputs the expected:

Could call with reference
Could call with pointer
Couldn't call

With MSVC, I get... well, a compiler error:

1>src\main.cpp(20): error C2995: ''unknown-type' test(C,F)' : function template has already been defined
1>          src\main.cpp(11) : see declaration of 'test'

It also seems that GCC 4.7.1 isn't quite up to the task:

source.cpp: In substitution of 'template decltype ((c.*f(), void())) test(C, F) [with C = X*; F = void (X::*)()]':
source.cpp:29:17:   required from here
source.cpp:11:6: error: cannot apply member pointer 'f' to 'c', which is of non-class type 'X*'
source.cpp: In substitution of 'template decltype ((c.*f(), void())) test(C, F) [with C = int; F = int]':
source.cpp:30:16:   required from here
source.cpp:11:6: error: 'f' cannot be used as a member pointer, since it is of type 'int'

A common use of Expression SFINAE is when defining traits, like a trait to check if a class sports a certain member function:

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)) {};

Live example. (Which, surprisingly, works again on GCC 4.7.1.)

See also this answer of mine, which uses the same technique in another environment (aka without traits).


Normative wording:

§14.8.2 [temp.deduct]

p6 At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

p7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions.

p8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...]

Xenomorphic answered 29/9, 2012 at 16:10 Comment(3)
I was wondering why suddenly there appeared to be links to liveworkspace instead of ideone, when I noticed that liveworkspace is using gcc 4.7.2. Ah! Finally!Pontus
The live example link is broken :(Soothfast
Compiles fine in MSVC 2017.Thurlough

© 2022 - 2024 — McMap. All rights reserved.