A falha de substituição não é um erro - Substitution failure is not an error

A falha de substituição não é um erro ( SFINAE ) refere-se a uma situação em C ++ em que uma substituição inválida de parâmetros de modelo não é em si um erro. David Vandevoorde introduziu pela primeira vez o acrônimo SFINAE para descrever técnicas de programação relacionadas.

Especificamente, ao criar um conjunto candidato para resolução de sobrecarga , alguns (ou todos) candidatos desse conjunto podem ser o resultado de modelos instanciados com argumentos de modelo (potencialmente deduzidos) substituídos pelos parâmetros de modelo correspondentes. Se ocorrer um erro durante a substituição de um conjunto de argumentos para qualquer modelo, o compilador remove a sobrecarga potencial do conjunto candidato em vez de parar com um erro de compilação, desde que o erro de substituição seja aquele que o padrão C ++ concede tal tratamento. Se um ou mais candidatos permanecerem e a resolução de sobrecarga for bem-sucedida, a chamada está bem formada.

Exemplo

O exemplo a seguir ilustra uma instância básica de SFINAE:

struct Test {
  typedef int foo;
};

template <typename T>
void f(typename T::foo) {}  // Definition #1

template <typename T>
void f(T) {}  // Definition #2

int main() {
  f<Test>(10);  // Call #1.
  f<int>(10);   // Call #2. Without error (even though there is no int::foo)
                // thanks to SFINAE.
}

Aqui, a tentativa de usar um tipo não-classe em um nome qualificado ( T::foo) resulta em uma falha de dedução f<int>porque intnão tem nenhum tipo aninhado nomeado foo, mas o programa está bem formado porque uma função válida permanece no conjunto de funções candidatas.

Embora SFINAE tenha sido inicialmente introduzido para evitar a criação de programas malformados quando declarações de template não relacionadas eram visíveis (por exemplo, através da inclusão de um arquivo de cabeçalho), muitos desenvolvedores mais tarde acharam o comportamento útil para introspecção em tempo de compilação. Especificamente, ele permite que um modelo determine certas propriedades de seus argumentos de modelo no momento da instanciação.

Por exemplo, SFINAE pode ser usado para determinar se um tipo contém um certo typedef:

#include <iostream>

template <typename T>
struct has_typedef_foobar {
  // Types "yes" and "no" are guaranteed to have different sizes,
  // specifically sizeof(yes) == 1 and sizeof(no) == 2.
  typedef char yes[1];
  typedef char no[2];

  template <typename C>
  static yes& test(typename C::foobar*);

  template <typename>
  static no& test(...);

  // If the "sizeof" of the result of calling test<T>(nullptr) is equal to
  // sizeof(yes), the first overload worked and T has a nested type named
  // foobar.
  static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};

struct foo {
  typedef float foobar;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << has_typedef_foobar<int>::value << std::endl;  // Prints false
  std::cout << has_typedef_foobar<foo>::value << std::endl;  // Prints true
}

Quando To tipo aninhado está foobardefinido, a instanciação do primeiro testfunciona e a constante de ponteiro nulo é passada com sucesso. (E o tipo resultante da expressão é yes.) Se não funcionar, a única função disponível é a segunda test, e o tipo resultante da expressão é no. Uma reticência é usada não apenas porque aceitará qualquer argumento, mas também porque sua classificação de conversão é a mais baixa, portanto, uma chamada para a primeira função será preferida, se possível; isso remove a ambiguidade.

Simplificação C ++ 11

No C ++ 11 , o código acima pode ser simplificado para:

#include <iostream>
#include <type_traits>

template <typename... Ts>
using void_t = void;

template <typename T, typename = void>
struct has_typedef_foobar : std::false_type {};

template <typename T>
struct has_typedef_foobar<T, void_t<typename T::foobar>> : std::true_type {};

struct foo {
  using foobar = float;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << has_typedef_foobar<int>::value << std::endl;
  std::cout << has_typedef_foobar<foo>::value << std::endl;
}

Com a padronização do idioma de detecção na proposta da Biblioteca fundamental v2 (n4562) , o código acima poderia ser reescrito da seguinte forma:

#include <iostream>
#include <type_traits>

template <typename T>
using has_typedef_foobar_t = typename T::foobar;

struct foo {
  using foobar = float;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << std::is_detected<has_typedef_foobar_t, int>::value << std::endl;
  std::cout << std::is_detected<has_typedef_foobar_t, foo>::value << std::endl;
}

Os desenvolvedores do Boost usaram SFINAE em boost :: enable_if e de outras maneiras.

Referências