Namensräume
Varianten
Aktionen

SFINAE

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 
 
 

"Substitution Failure Is Not An Error"


Diese Regel gilt während der Überladungsauflösung von Funktionsvorlagen: Wenn das Ersetzen des explizit angegebenen oder abgeleiteten Typs für den Vorlagenparameter fehlschlägt, wird die Spezialisierung aus der Überladungsmenge verworfen, anstatt einen Kompilierungsfehler zu verursachen.

Dieses Feature wird in der Template-Metaprogrammierung verwendet.

Inhalt

[edit] Erklärung

Funktionsvorlagenparameter werden zweimal ersetzt (durch Vorlagenargumente)

  • explizit angegebene Vorlagenargumente werden vor der Vorlagenargumentableitung ersetzt
  • abgeleitete Argumente und aus Standardwerten gewonnene Argumente werden nach der Vorlagenargumentableitung ersetzt

Ersetzung findet statt in

  • allen Typen, die im Funktionstyp verwendet werden (einschließlich Rückgabetyp und Typen aller Parameter)
  • allen Typen, die in den Vorlagenparametrisierungsklauseln verwendet werden
  • allen Typen, die in der Liste der Vorlagenargumente einer partiellen Spezialisierung verwendet werden
  • allen Ausdrücken, die im Funktionstyp verwendet werden
  • allen Ausdrücken, die in einer Vorlagenparametrisierungsklausel verwendet werden
  • allen Ausdrücken, die in der Liste der Vorlagenargumente einer partiellen Spezialisierung verwendet werden
(seit C++11)
(seit C++20)

Ein Substitutionsfehler ist jede Situation, in der der obige Typ oder Ausdruck fehlerhaft wäre (mit einer erforderlichen Diagnose), wenn er mit den ersetzten Argumenten geschrieben würde.

Nur Fehler in den Typen und Ausdrücken im unmittelbaren Kontext des Funktionstyps oder seiner Vorlagenparametertypen oder seines expliziten Spezifizierers(seit C++20) sind SFINAE-Fehler. Wenn die Auswertung eines ersetzten Typs/Ausdrucks Nebeneffekte wie die Instanziierung einer Vorlagenspezialisierung, die Generierung einer implizit definierten Member-Funktion usw. verursacht, werden Fehler in diesen Nebeneffekten als harte Fehler behandelt. Ein Lambda-Ausdruck wird nicht als Teil des unmittelbaren Kontexts betrachtet.(seit C++20)

Die Ersetzung erfolgt in lexikalischer Reihenfolge und stoppt, wenn ein Fehler auftritt.

Wenn es mehrere Deklarationen mit unterschiedlichen lexikalischen Ordnungen gibt (z. B. eine Funktionsvorlage, die mit einem nachgestellten Rückgabetyp deklariert wird, um nach einem Parameter ersetzt zu werden, und mit einem gewöhnlichen Rückgabetyp neu deklariert wird, der vor dem Parameter ersetzt werden würde), und dies dazu führen würde, dass Vorlageninstanziierungen in einer anderen Reihenfolge oder gar nicht auftreten, dann ist das Programm fehlerhaft; keine Diagnose erforderlich.

(seit C++11)
template<typename A>
struct B { using type = typename A::type; };
 
template<
    class T,
    class U = typename T::type,    // SFINAE failure if T has no member type
    class V = typename B<T>::type> // hard error if B has no member type
                                   // (guaranteed to not occur via CWG 1227 because
                                   // substitution into the default template argument
                                   // of U would fail first)
void foo (int);
 
template<class T>
typename T::type h(typename B<T>::type);
 
template<class T>
auto h(typename B<T>::type) -> typename T::type; // redeclaration
 
template<class T>
void h(...) {}
 
using R = decltype(h<int>(0));     // ill-formed, no diagnostic required

[edit] Type SFINAE

Die folgenden Typfehler sind SFINAE-Fehler

  • Versuch, eine Pack-Erweiterung zu instanziieren, die mehrere Packs unterschiedlicher Länge enthält
(seit C++11)
  • Versuch, ein Array von void, ein Array von Referenzen, ein Array von Funktionen, ein Array mit negativer Größe, ein Array mit nicht-ganzzahliger Größe oder ein Array der Größe Null zu erstellen
template<int I>
void div(char(*)[I % 2 == 0] = nullptr)
{
    // this overload is selected when I is even
}
 
template<int I>
void div(char(*)[I % 2 == 1] = nullptr)
{
    // this overload is selected when I is odd
}
  • Versuch, einen Typ links von einem Scope-Auflösungsoperator `::` zu verwenden, und dieser ist keine Klasse oder Aufzählung
template<class T>
int f(typename T::B*);
 
template<class T>
int f(T);
 
int i = f<int>(0); // uses second overload
  • Versuch, ein Member eines Typs zu verwenden, wobei
  • der Typ den angegebenen Member nicht enthält
  • der angegebene Member kein Typ ist, wo ein Typ erforderlich ist
  • der angegebene Member keine Vorlage ist, wo eine Vorlage erforderlich ist
  • der angegebene Member kein Nicht-Typ ist, wo ein Nicht-Typ erforderlich ist
template<int I>
struct X {};
 
template<template<class T> class>
struct Z {};
 
template<class T>
void f(typename T::Y*) {}
 
template<class T>
void g(X<T::N>*) {}
 
template<class T>
void h(Z<T::template TT>*) {}
 
struct A {};
struct B { int Y; };
struct C { typedef int N; };
struct D { typedef int TT; };
struct B1 { typedef int Y; };
struct C1 { static const int N = 0; };
struct D1
{ 
    template<typename T>
    struct TT {}; 
};
 
int main()
{
    // Deduction fails in each of these cases:
    f<A>(0); // A does not contain a member Y
    f<B>(0); // The Y member of B is not a type
    g<C>(0); // The N member of C is not a non-type
    h<D>(0); // The TT member of D is not a template
 
    // Deduction succeeds in each of these cases:
    f<B1>(0); 
    g<C1>(0); 
    h<D1>(0);
}
// todo: needs to demonstrate overload resolution, not just failure
  • Versuch, einen Zeiger auf eine Referenz zu erstellen
  • Versuch, eine Referenz auf void zu erstellen
  • Versuch, einen Zeiger auf ein Member von T zu erstellen, wobei T kein Klassentyp ist
template<typename T>
class is_class
{
    typedef char yes[1];
    typedef char no[2];
 
    template<typename C>
    static yes& test(int C::*); // selected if C is a class type
 
    template<typename C>
    static no& test(...);       // selected otherwise
public:
    static bool const value = sizeof(test<T>(nullptr)) == sizeof(yes);
};
  • Versuch, einem Nicht-Typ-Vorlagenparameter einen ungültigen Typ zuzuweisen
template<class T, T>
struct S {};
 
template<class T>
int f(S<T, T()>*);
 
struct X {};
int i0 = f<X>(0);
// todo: needs to demonstrate overload resolution, not just failure
  • Versuch, eine ungültige Konvertierung durchzuführen in
  • in einem Vorlagenargumentausdruck
  • in einem im Funktionsdeklaration verwendeten Ausdruck
template<class T, T*> int f(int);
int i2 = f<int, 1>(0); // can’t conv 1 to int*
// todo: needs to demonstrate overload resolution, not just failure
  • Versuch, einen Funktionstyp mit einem Parameter vom Typ void zu erstellen
  • Versuch, einen Funktionstyp zu erstellen, der einen Array-Typ oder einen Funktionstyp zurückgibt

[edit] Expression SFINAE

Nur konstante Ausdrücke, die in Typen (wie Array-Grenzen) verwendet wurden, mussten vor C++11 als SFINAE (und nicht als harte Fehler) behandelt werden.

(bis C++11)

Die folgenden Ausdrucksfehler sind SFINAE-Fehler

  • Fehlerhafter Ausdruck, der in einem Vorlagenparametertyp verwendet wird
  • Fehlerhafter Ausdruck, der im Funktionstyp verwendet wird
struct X {};
struct Y { Y(X){} }; // X is convertible to Y
 
template<class T>
auto f(T t1, T t2) -> decltype(t1 + t2); // overload #1
 
X f(Y, Y);                               // overload #2
 
X x1, x2;
X x3 = f(x1, x2); // deduction fails on #1 (expression x1 + x2 is ill-formed)
                  // only #2 is in the overload set, and is called
(seit C++11)

[edit] SFINAE in partiellen Spezialisierungen

Ableitung und Ersetzung erfolgen auch bei der Bestimmung, ob eine Spezialisierung einer Klassen- oder Variablen-(seit C++14) Vorlage durch eine partielle Spezialisierung oder die Primärvorlage generiert wird. Ein Substitutionsfehler wird bei einer solchen Bestimmung nicht als harter Fehler behandelt, sondern macht die entsprechende partielle Spezialisierungsdeklaration stattdessen ignoriert, als ob bei der Überladungsauflösung von Funktionsvorlagen.

// primary template handles non-referenceable types:
template<class T, class = void>
struct reference_traits
{
    using add_lref = T;
    using add_rref = T;
};
 
// specialization recognizes referenceable types:
template<class T>
struct reference_traits<T, std::void_t<T&>>
{
    using add_lref = T&;
    using add_rref = T&&;
};
 
template<class T>
using add_lvalue_reference_t = typename reference_traits<T>::add_lref;
 
template<class T>
using add_rvalue_reference_t = typename reference_traits<T>::add_rref;

[edit] Bibliotheksunterstützung

Die Standardbibliothekskomponente std::enable_if ermöglicht die Erzeugung eines Substitutionsfehlers, um bestimmte Überladungen basierend auf einer zur Kompilierungszeit ausgewerteten Bedingung zu aktivieren oder zu deaktivieren.

Darüber hinaus müssen viele Typ-Traits mit SFINAE implementiert werden, wenn geeignete Compiler-Erweiterungen nicht verfügbar sind.

(seit C++11)

Die Standardbibliothekskomponente std::void_t ist eine weitere Utility-Metafunktion, die partielle Spezialisierungsanwendungen von SFINAE vereinfacht.

(seit C++17)

[edit] Alternativen

Wo anwendbar, werden Tag-Dispatch, if constexpr(seit C++17) und Konzepte(seit C++20) normalerweise der Verwendung von SFINAE vorgezogen.

static_assert wird normalerweise SFINAE vorgezogen, wenn nur ein bedingter Kompilierungszeitfehler gewünscht ist.

(seit C++11)

[edit] Beispiele

Ein gängiges Idiom ist die Verwendung von Expression SFINAE für den Rückgabetyp, wobei der Ausdruck den Komma-Operator verwendet, dessen linke Unterausdruck derjenige ist, der untersucht wird (auf void gecastet, um sicherzustellen, dass der benutzerdefinierte Komma-Operator des Rückgabetyps nicht ausgewählt wird), und der rechte Unterausdruck den Typ hat, den die Funktion zurückgeben soll.

#include <iostream>
 
// This overload is added to the set of overloads if C is
// a class or reference-to-class type and F is a pointer to member function of C
template<class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "(1) Class/class reference overload called\n";
}
 
// This overload is added to the set of overloads if C is a
// pointer-to-class type and F is a pointer to member function of C
template<class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "(2) Pointer overload called\n";
}
 
// This overload is always in the set of overloads: ellipsis
// parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "(3) Catch-all overload called\n";
}
 
int main()
{
    struct X { void f() {} };
    X x;
    X& rx = x;
    test(x, &X::f);  // (1)
    test(rx, &X::f); // (1), creates a copy of x
    test(&x, &X::f); // (2)
    test(42, 1337);  // (3)
}

Ausgabe

(1) Class/class reference overload called
(1) Class/class reference overload called
(2) Pointer overload called
(3) Catch-all overload called

[edit] Fehlerberichte

Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 295 C++98 Erstellung eines cv-qualifizierten Funktionstyps
kann zu Substitutionsfehler führen
wird nicht als Fehler behandelt,
indem cv-Qualifizierung verworfen wird
CWG 1227 C++98 die Reihenfolge der Ersetzung war nicht spezifiziert dasselbe wie die lexikalische Reihenfolge
CWG 2054 C++98 Ersetzung in partiellen Spezialisierungen war nicht korrekt spezifiziert spezifiziert
CWG 2322 C++11 Deklarationen in unterschiedlichen lexikalischen Ordnungen würden Vorlagen verursachen
Instanziierungen treten in anderer Reihenfolge oder gar nicht auf
ein solcher Fall ist fehlerhaft,
keine Diagnose erforderlich