Verwendung von Enum-Werten in Kombination mit SFINAE
Ich bin bereits mit SFINAE vertraut und weiß, wie es verwendet werden kann, um bestimmte Vorlagen basierend auf dem übergebenen Typ zu aktivieren (mithilfe von std :: enable_if). Ich habe jedoch kürzlich begonnen, an einem Projekt zu arbeiten, in dem ich Folgendes tun möchte: Erstellen Sie eine Klassenspezialisierung basierend auf dem angegebenen Enum-Wert, während Sie SFINAE verwenden. Jetzt weiß ich, dass es möglich ist, Spezialisierungen basierend auf einem Aufzählungswert vorzunehmen, wenn man bedenkt, dass ich dies zuvor getan habe (wie folgt):
enum Specifier
{
One,
Two,
Three
}
template <Specifier>
class Foo
{
public:
void Bar();
}
template<>
void Foo<Specifier::One>::Bar()
{
}
Jetzt möchte ich jedoch SFINAE verwenden, um eine bestimmte Spezialisierung Bar()
für mehrere Aufzählungswerte zu verwenden. Etwas wie das:
template <Specifier Type>
class Foo
{
public:
template <typename std::enable_if<Type == Specifier::Two || Type == Specifier::One, void>::type>
void Bar();
template <typename std::enable_if<Type == Specifier::Three, void>::type>
void Bar();
}
Irgendeine Idee, ob dies möglich ist, und wenn ja, wie würde ich das tun?
Antworten
C ++ 17: constexpr if
Ab C ++ 17 können Sie eine Überladung mit einer einzelnen Elementfunktion verwenden (anstelle mehrerer Überladungen, die über SFINAE vorhanden sind oder nicht), deren Körper constexpr nutzt, wenn:
#include <iostream>
enum class Specifier { One, Two, Three };
template <Specifier S> class Foo {
public:
static constexpr int bar() {
if constexpr ((S == Specifier::One) || (S == Specifier::Two)) {
return 12;
} else if constexpr (S == Specifier::Three) {
return 3;
}
}
};
int main() {
std::cout << Foo<Specifier::One>::bar() << "\n" // 12
<< Foo<Specifier::Two>::bar() << "\n" // 12
<< Foo<Specifier::Three>::bar(); // 3
}
C ++ 11: SFINAE und std::enable_if
( _t
) (C ++ 14)
Sie können SFINAE ebenfalls mit der Anforderung verwenden, dass Ihre Nicht-Vorlagen-Elementfunktionen zu Elementfunktionsvorlagen mit einem Dummy-Vorlagenparameter gemacht werden müssen, da SFINAE in jeder Funktionsdeklaration auf einen abhängigen Namen und eine Klassenvorlage (Typ oder) angewendet werden muss Nicht-Typ) -Parameter ist natürlich kein abhängiger Name in der Deklaration einer Nicht-Template- Member-Funktion:
template <Specifier S> class Foo {
public:
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::One) || (S_ == Specifier::Two)>
* = nullptr>
static constexpr int bar() {
return 12;
}
template <Specifier S_ = S,
std::enable_if_t<(S_ == Specifier::Three)> * = nullptr>
static constexpr int bar() {
return 3;
}
};
Beachten Sie, dass im obigen Beispiel std::enable_if_t
die in C ++ 14 eingeführte Hilfsaliasvorlage verwendet wird. Wenn Sie C ++ 11 verwenden, müssen Sie typename std::enable_if<..>::type
stattdessen verwenden.
Beachten Sie außerdem, dass ein missbräuchlicher Benutzer das Standardvorlagenargument für den (Dummy-) Nicht-Typ-Vorlagenparameter überschreiben kann, da wir die Elementfunktionen mit Vorlagen versehen müssen S_
:
Foo<Specifier::One>::bar<Specifier::Three>(); // 3
Daher möchten wir dem std::enable_if_t
Prädikat möglicherweise für jede Überladung eine zusätzliche UND-Bedingung hinzufügen , nämlich (S_ == S) && (... predicate as above)
. Wie wir im folgenden Abschnitt sehen werden, ist dies in C ++ 20 kein Problem mehr, da wir vermeiden können, Nicht-Vorlagenelementfunktionen ausschließlich für die Anwendung von SFINAE zu Vorlagen zu machen.
Alternative Verwendung von Spezialisierungen anstelle von Überladung
Wie ich auch in der folgenden Antwort auf eine Folgefrage zu dieser Frage gezeigt habe, können Sie SFINAE auch in der Liste der Vorlagenargumente (auf die teilweise spezialisierte Klassenvorlage) einer Spezialisierung anwenden:
template <Specifier, typename = void> struct Foo {
static constexpr int bar() { return 1; } // default
};
template <Specifier S>
struct Foo<S,
std::enable_if_t<(S == Specifier::One) || (S == Specifier::Two)>> {
static constexpr int bar() { return 12; }
};
C ++ 20: Nicht-Template-Member-Funktionen von Klassenvorlagen verwenden möglicherweise die require-Klausel : s
Ab C ++ 20 können Sie eine Nicht-Template-Member-Funktion einer Klassenvorlage mithilfe einer abschließenden require-Klausel mit gegenseitigen ausschließlichen Einschränkungen für jede überladene Funktion überladen und einschränken :
template <Specifier S> class Foo {
public:
static constexpr int bar() requires((S == Specifier::One) ||
(S == Specifier::Two)) {
return 12;
}
static constexpr int bar() requires(S == Specifier::Three) { return 3; }
};