Что такое "выражение SFINAE"?

В http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx команда VС++ официально заявляет, что еще не реализовала основную функцию С++ 11 "Expression SFINAE". Тем не менее, следующие примеры кода, скопированные из http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html, принимаются компилятором VС++.

пример 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"?

Ответы

Ответ 1

Выражение SFINAE объясняется достаточно хорошо в документе, который вы связали, я думаю. Это SFINAE в выражениях. Если выражение внутри decltype недействительно, хорошо, удалите функцию из VIP-зала перегрузок. Вы можете найти нормативную формулировку в конце этого ответа.

Заметка о VС++: они не реализовали ее полностью. На простых выражениях это может сработать, но на других это не будет. См. Обсуждение в комментариях в этом ответе для примеров, которые терпят неудачу. Чтобы сделать его простым, это не сработает:

#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)' : function template has already been defined
1>          src\main.cpp(11) : see declaration of 'test'

Также кажется, что GCC 4.7.1 не совсем соответствует задаче:

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'

Общепринятое использование выражения 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 Подстановка происходит во всех типах и выражениях, которые используются в типе функции и в объявлениях параметров шаблона. Выражения включают не только постоянные выражения, такие как те, которые появляются в границах массива или как аргументы шаблона nontype , но также общие выражения (т.е. непостоянные выражения) внутри sizeof, decltype и других контекстах, которые допускают не константные выражения.

p8 Если подстановка приводит к недопустимому типу или выражению, тип дедукции терпит неудачу. Недопустимый тип или выражение - это те, которые были бы плохо сформированы, если они были написаны с использованием замещенных аргументов. [...]