wake-up-neo.com

C ++ 14 Variable Templates: Wozu dienen sie? Ein Anwendungsbeispiel?

In C++ 14 können Variablen mit Vorlagen erstellt werden. Das übliche Beispiel ist eine Variable 'pi', die gelesen werden kann, um den Wert der mathematischen Konstante π für verschiedene Typen zu erhalten (3 für int; der nächstmögliche Wert mit float usw.).

Abgesehen davon können wir diese Funktion haben, indem wir einfach eine Variable in eine Struktur oder Klasse mit Vorlagen einschließen. Wie verhält sich das mit Typkonvertierungen? Ich sehe einige Überlappungen.

Und anders als im Pi-Beispiel, wie würde es mit Nicht-Konstanten-Variablen funktionieren? Gibt es ein Anwendungsbeispiel, um zu verstehen, wie man ein solches Feature optimal nutzt und wozu es dient?

52
jbgs

Und anders als im Pi-Beispiel, wie würde es mit Nicht-Konstanten-Variablen funktionieren?

Derzeit scheint es, die Variablen separat für den Typ zu instanziieren. Sie können also n<int> eine 10 zuweisen, die sich von der Vorlagendefinition unterscheidet.

template<typename T>
T n = T(5);

int main()
{
    n<int> = 10;
    std::cout << n<int> << " ";    // 10
    std::cout << n<double> << " "; // 5
}

Wenn die Deklaration const ist, ist sie schreibgeschützt. Wenn es sich um eine constexpr handelt, hat es, wie alle constexpr -Deklarationen, außerhalb von constexpr (Ressionen) wenig Verwendung.

Abgesehen davon können wir diese Funktion haben, indem wir einfach eine Variable in eine Struktur oder Klasse mit Vorlagen einschließen. Wie verhält sich das mit Typkonvertierungen?

Es soll ein einfacher Vorschlag sein. Ich kann nicht erkennen, wie sich dies auf die Typkonvertierungen auswirkt. Wie bereits erwähnt, ist der Typ der Variablen der Typ, mit dem Sie die Vorlage instanziiert haben. d.h. decltype(n<int>) ist int. decltype((double)n<int>) ist double und so weiter.

Gibt es ein Anwendungsbeispiel, um zu verstehen, wie man ein solches Feature optimal nutzt und wozu es dient?

N3651 liefert eine kurze Begründung.

Leider erlauben vorhandene C++ - Regeln einer Vorlagendeklaration nicht, eine Variable zu deklarieren. Es gibt bekannte Problemumgehungen für dieses Problem:

• Verwenden Sie statische constexpr-Datenelemente von Klassenvorlagen

• Verwenden Sie constexpr-Funktionsvorlagen, die die gewünschten Werte zurückgeben

Diese Problemumgehungen sind seit Jahrzehnten bekannt und gut dokumentiert. Standardklassen wie std :: numeric_limits sind typische Beispiele. Obwohl diese Problemumgehungen nicht perfekt sind, waren ihre Nachteile bis zu einem gewissen Grad tolerierbar, da in der C++ 03-Ära nur einfache eingebaute Typkonstanten eine uneingeschränkte direkte und effiziente Unterstützung für die Kompilierungszeit hatten. All dies änderte sich mit der Einführung von constexpr-Variablen in C++ 11, wodurch die direkte und effiziente Unterstützung auf Konstanten benutzerdefinierter Typen ausgeweitet wurde. Jetzt machen Programmierer Konstanten (von Klassentypen) in Programmen immer deutlicher. Wachsen Sie so die Verwirrung und die Frustration, die mit den Problemumgehungen verbunden sind.

...

Die Hauptprobleme bei "statischen Datenelementen" sind:

• Sie erfordern "doppelte" Deklarationen: Einmal innerhalb der Klassenvorlage, einmal außerhalb der Klassenvorlage, um die "echte" Definition für den Fall bereitzustellen, dass die Konstanten nicht verwendet werden.

• Programmierer sind sowohl verärgert als auch verwirrt über die Notwendigkeit, zweimal dieselbe Erklärung abzugeben. Im Gegensatz dazu benötigen "normale" Konstantendeklarationen keine doppelten Deklarationen.

...

Bekannte Beispiele in dieser Kategorie sind wahrscheinlich statische Elementfunktionen von numeric_limits oder Funktionen wie boost::constants::pi<T>() usw. Constexpr-Funktionsschablonen leiden nicht unter dem Problem "doppelter Deklarationen", das statische Datenelemente haben. darüber hinaus bieten sie eine funktionale Abstraktion. Sie zwingen den Programmierer jedoch, vorab an der Definitionsstelle festzulegen, wie die Konstanten geliefert werden sollen: entweder durch eine Konstantenreferenz oder durch einen einfachen Nichtreferenztyp. Wenn sie als Konstantenreferenz geliefert werden, müssen die Konstanten systematisch im statischen Speicher zugewiesen werden. Wenn es sich um einen Nichtreferenztyp handelt, müssen die Konstanten kopiert werden. Kopieren ist kein Problem für eingebaute Typen, aber es ist ein Showstopper für benutzerdefinierte Typen mit Wertsemantik, die nicht nur winzige eingebaute Typen umschließen (z. B. Matrix, Ganzzahl, Bigfloat usw.). gewöhnliche "const (expr)" - Variablen leiden nicht unter diesem Problem. Es wird eine einfache Definition bereitgestellt, und die Entscheidung, ob die Konstanten tatsächlich im Speicher abgelegt werden müssen, hängt nur von der Verwendung ab, nicht von der Definition.

25
user1508519

wir können diese Funktion haben, indem wir eine Variable in eine Struktur oder Klasse mit Vorlagen einschließen

Ja, aber das wäre ein unentgeltliches syntaktisches Salz. Nicht gesund für den Blutdruck.

pi<double> vermittelt die Absicht besser als pi<double>::value. Kurz und bündig. Das ist Grund genug in meinem Buch, diese Syntax zuzulassen und zu fördern.

23
n.m.

Ein weiteres praktisches Beispiel für die variablen Vorlagen von C++ 14 ist, wenn Sie eine Funktion benötigen, um etwas an std::accumulate Zu übergeben:

template<typename T>
T const & (*maxer) (T const &, T const &) = std::max<T>;

std::accumulate(some.begin(), some.end(), initial, maxer<float>);

Beachten Sie, dass die Verwendung von std::max<T> Nicht ausreicht, da daraus nicht die genaue Signatur abgeleitet werden kann. In diesem speziellen Beispiel können Sie stattdessen max_element Verwenden, aber der Punkt ist, dass es eine ganze Klasse von Funktionen gibt, die dieses Verhalten teilen.

7
Levi Morrison

Ich frage mich, ob etwas in diese Richtung möglich wäre: (vorausgesetzt, die Vorlage Lambdas ist verfügbar)

void some_func() {
    template<typename T>
    std::map<int, T> storage;

    auto store = []<typename T>(int key, const T& value) { storage<T>[key] = value; };

    store(0, 2);
    store(1, "Hello"s);
    store(2, 0.7);

    // All three values are stored in a different map, according to their type. 
}

Nun, ist das nützlich?

Beachten Sie zur einfacheren Verwendung, dass die Initialisierung von pi<T> Eine explizite Konvertierung (expliziter Aufruf eines unären Konstruktors) und keine einheitliche Initialisierung verwendet. Dies bedeutet, dass Sie bei einem gegebenen Typ radians mit einem Konstruktor radians(double)pi<radians> Schreiben können.

6

Nun, Sie können dies verwenden, um Kompilierungs-Zeitcode wie folgt zu schreiben:

#include <iostream>

template <int N> const int ctSquare = N*N;

int main() {
    std::cout << ctSquare<7> << std::endl;
}

Dies ist eine signifikante Verbesserung gegenüber dem Äquivalent

#include <iostream>

template <int N> struct ctSquare {
    static const int value = N*N;
};

int main() {
    std::cout << ctSquare<7>::value << std::endl;
}

diese Leute haben früher geschrieben, um die Metaprogrammierung von Vorlagen durchzuführen, bevor variable Vorlagen eingeführt wurden. Für Werte ohne Typ konnten wir dies seit C++ 11 mit constexpr tun, sodass Vorlagenvariablen nur den Vorteil haben, Berechnungen basierend auf Typen für die Variablenvorlagen zuzulassen.

TL; DR: Sie erlauben uns nicht, etwas zu tun, was wir vorher nicht konnten, aber sie machen die Metaprogrammierung von Vorlagen weniger zu einer PITA.

2
cmaster