Es ist möglich, dies zu tun, wenn Sie die Tatsache nutzen, dass bestimmte Ausdrücke an Stellen verwendet werden, an denen constexpr
erwartet werden, und dass Sie abfragen können, wie der Status für jeden Kandidaten ist. Speziell in unserem Fall ist die Tatsache, dass constexpr
s ohne Definition nicht als konstante Ausdrücke übergeben werden können und noexcept
eine Garantie für konstante Ausdrücke ist. Daher gibt noexcept(...)
, das true
zurückgibt, das Vorhandensein eines ordnungsgemäß definierten constexpr
an.
Im Wesentlichen behandelt dies constexpr
s als Ja/Nein-Schalter und führt den Status zur Kompilierzeit ein.
Beachten Sie, dass dies ein Hack ist, benötigen Sie Workarounds für bestimmte Compiler (siehe die Artikel voraus) und diese spezifische friend
-basierte Implementierung könnte als fehlerhaft durch zukünftige Überarbeitungen des Standards betrachtet werden.
Mit diesem aus dem Weg ...
Benutzer Filip Roséen stellt dieses Konzept in his article es speziell gewidmet ist.
Seine Beispiel-Implementierung ist, mit zitierte Erklärungen:
constexpr int flag (int);
A constexpr Funktion in einem von zwei Zuständen sein kann; Entweder ist es verwendbar in einem konstanten Ausdruck, oder es ist nicht - wenn es eine Definition fehlt, fällt es automatisch in die letztere Kategorie - es gibt keinen anderen Staat (außer wir betrachten undefiniertes Verhalten).
Normalerweise sollten conexpr Funktionen genau so behandelt werden, wie sie sind; Funktionen, aber wir können sie auch als individuelle Handles zu "Variablen" mit einem Typ ähnlich wie Bool, wo jede "Variable" kann einen von zwei Werten haben; verwendbar oder nicht verwendbar.
In unserem Programm hilft es, wenn Sie die Flagge als genau das betrachten; ein Handle (keine Funktion). Der Grund dafür ist, dass wir das Flag niemals in einem evaluierten Kontext aufrufen werden, wir sind nur an seinem aktuellen Zustand interessiert.
template<class Tag>
struct writer {
friend constexpr int flag (Tag) {
return 0;
}
};
writer ist eine Klasse-Vorlage, die bei der Instanziierung, eine Definition für eine Funktion in dem umgebenden Namensraum (mit der Signatur int-Flag (Tag) schaffen, in dem ein Tag ist template- Parameter).
Wenn wir wieder einmal von constexpr Funktionen denken als bis zu einem gewissen Variable behandelt, können wir eine Instanziierung des Schriftstellers als unbedingtes Schreiben des Wertes verwendbar auf die Variable hinter der Funktion in der Freund-Erklärung behandeln.
template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };
wäre ich nicht überrascht, wenn Sie denken, dependent_writer wie ein ziemlich sinnlos indirection aussieht; Warum nicht direkt instantiieren Schriftsteller , wo wir es verwenden möchten, anstatt durch dependent_writer gehen?
- Instantiierung des Schreibers muss von etwas abhängen, um sofortige Instanziierung zu verhindern, und;
- dependent_writer wird in einem Kontext verwendet, in dem ein Wert vom Typ bool als Abhängigkeit verwendet werden kann.
template<
bool B = noexcept (flag (0)),
int = sizeof (dependent_writer<B>)
>
constexpr int f() {
return B;
}
Die oben vielleicht ein wenig seltsam aussehen, aber es ist wirklich ganz einfach;
- setzt B = wahr, wenn Flag (0) ein konstanter Ausdruck ist, andernfalls B = false, und;
- implizit instanziiert instant_writer (sizeof benötigt einen vollständig definierten Typ).
Das Verhalten kann mit dem folgenden Pseudocode ausgedrückt werden:
IF [ `int flag (int)` has not yet been defined ]:
SET `B` = `false`
INSTANTIATE `dependent_writer<false>`
RETURN `false`
ELSE:
SET `B` = `true`
INSTANTIATE `dependent_writer<true>`
RETURN `true`
Schließlich ist der Proof of Concept:
int main() {
constexpr int a = f();
constexpr int b = f();
static_assert (a != b, "fail");
}
ich dies auf Ihre Anwendung Problem. Die Idee ist, die constexpr
Ja/Nein-Schalter zu verwenden, um anzuzeigen, ob ein Typ instanziiert wurde. Sie benötigen also einen separaten Schalter für jeden Typ, den Sie haben.
template<typename T>
struct inst_check_wrapper
{
friend constexpr int inst_flag(inst_check_wrapper<T>);
};
inst_check_wrapper<T>
wickelt im Wesentlichen einen Schalter für wie auch immer geartete Sie es geben kann. Es ist nur eine generische Version des ursprünglichen Beispiels.
template<typename T>
struct writer
{
friend constexpr int inst_flag(inst_check_wrapper<T>)
{
return 0;
}
};
Der Switch-Toggler ist identisch mit dem im ursprünglichen Beispiel. Es kommt mit der Definition für den Schalter eines Typs, den Sie verwenden.Um eine einfache Überprüfung zu ermöglichen, fügen Sie einen Helper Switch Inspector hinzu:
template <typename T, bool B = noexcept(inst_flag(inst_check_wrapper<T>()))>
constexpr bool is_instantiated()
{
return B;
}
Schließlich "registriert" sich der Typ selbst als initialisiert. In meinem Fall:
template <typename T>
struct MyStruct
{
template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)>
MyStruct()
{}
};
Der Schalter wird eingeschaltet, sobald dieser bestimmte Konstruktor gefragt wird. Beispiel:
int main()
{
static_assert(!is_instantiated<MyStruct<int>>(), "failure");
MyStruct<int> a;
static_assert(is_instantiated<MyStruct<int>>(), "failure");
}
Live on Coliru.
Könnten Sie bitte ein Beispiel mit dieser Art von Informationen in einem enable_if zeigen? – erenon
Die angenommene Antwort scheint auf neueren Compilern nicht zu funktionieren. Es scheint, dass die Antwort von einigen GCC-Compiler-Fehlern abhängt und die Lösung nicht mit standardkonformen Compilern funktioniert. –