Warum werden type_traits mit speziellen Template-Strukturen anstelle von constexpr implementiert?

7

Gibt es einen Grund, warum der Standard sie als Vorlage struct s anstelle von einfacher boolescher constexpr ? angibt?

In einer zusätzlichen Frage, die wahrscheinlich in einer guten Antwort auf die Hauptfrage beantwortet wird, wie würde man enable_if stuff mit den Nicht-Struktur-Versionen machen?

    
rubenvb 17.01.2012, 14:49
quelle

5 Antworten

17

Ein Grund ist, dass constexpr -Funktionen kein verschachteltes type -Member bereitstellen können, was in einigen Meta-Programmier-Situationen nützlich ist.

Um es klar zu machen, ich spreche nicht nur von Transformationsmerkmalen (wie make_unsigned ), die Typen erzeugen und offensichtlich nicht constexpr functions gemacht werden können. Alle Typeigenschaften bieten solche verschachtelten type -Mitglieder, sogar unäre Typeigenschaften und binäre Typeigenschaften . Zum Beispiel is_void<int>::type ist false_type .

Natürlich könnte dies mit std::integral_constant<bool, the_constexpr_function_version_of_some_trait<T>()> umgehen, aber es wäre nicht so praktisch.

In jedem Fall, wenn Sie wirklich Funktion-ähnliche Syntax wollen, ist das bereits möglich. Sie können einfach den Merkmalkonstruktor verwenden und die implizite consExpr-Konvertierung in integral_constant nutzen:

%Vor%

Für Transformationsmerkmale können Sie einen Vorlagenalias verwenden, um etwas zu erhalten, das dieser Syntax sehr nahe kommt:

%Vor%     
R. Martinho Fernandes 17.01.2012, 14:53
quelle
4

Beachte: Das sieht eher aus wie eine Tirade als eine richtige Antwort ... Ich hatte aber ein bisschen Juckreiz beim Lesen der vorherigen Antworten, also bitte entschuldigt mich;)

Erstens werden Klassenmerkmale historisch mit Templatestrukturen erstellt, da sie vor constexpr und decltype liegen. Ohne diese beiden war es ein wenig mehr Arbeit, Funktionen zu verwenden, obwohl die verschiedenen Bibliotheksimplementierungen von is_base_of Funktionen intern verwenden mussten, um die Vererbung richtig zu machen.

  

Was sind die Vorteile für die Verwendung von Funktionen?

  • Vererbung funktioniert einfach.
  • Syntax kann natürlicher sein ( typename ::type sieht dumm TM )
  • aus
  • eine gute Anzahl von Eigenschaften sind jetzt veraltet

Tatsächlich ist die Vererbung wahrscheinlich der Hauptpunkt gegen Klassenmerkmale. Es ist höllisch ärgerlich, dass du alle abgeleiteten Klassen spezialisieren musst, um momma zu machen. Sehr nervig. Mit Funktionen erben Sie einfach die Eigenschaften und können spezialisieren, wenn Sie möchten .

  

Was sind die Nachteile ?

  • Verpackung! Ein Strukturmerkmal kann mehrere Typen / Konstanten gleichzeitig einbetten.

Natürlich könnte man argumentieren, dass das eigentlich nervig ist: Spezialisiert auf iterator_traits , erbt ihr so ​​oft kostenlos von std::iterator_traits nur , um den Standard zu bekommen. Verschiedene Funktionen würden dies natürlich bereitstellen.

  

Konnte es funktionieren?

Nun, in einem Wort, wo alles constexpr basiert wäre, außer von enable_if (aber dann ist es kein Merkmal), würden Sie gehen:

%Vor%

Hinweis: Ich habe std::declval hier nicht benutzt, weil es einen unevaluierten Kontext benötigt (zB sizeof oder decltype meistens). Eine zusätzliche Anforderung (nicht sofort sichtbar) ist, dass T standardmäßig konstruierbar ist.

Wenn Sie wirklich wollen, gibt es einen Hack:

%Vor%
  

Und wenn ich einen Typ brauche, keine Konstante?

%Vor%

Ich sehe auch kein Problem (und ja, hier verwende ich declval ). Aber ... das Übergeben von Typen hat nichts mit constexpr zu tun; constexpr Funktionen sind nützlich, wenn sie Werte zurückgeben, an denen Sie interessiert sind. Funktionen, die komplexe Typen zurückgeben, können natürlich verwendet werden, aber sie sind nicht constexpr und Sie verwenden den Wert nicht vom Typ.

  

Und was, wenn ich Trais und Typen verketten muss?

Ausgerechnet hier leuchten Funktionen:)

%Vor%

Was! Betrüger! Es ist kein Merkmal definiert!

Hum ... eigentlich, das ist der Punkt. Mit decltype wurde eine gute Anzahl von Merkmalen gerade redundant .

TROCKEN !

  

Vererbung funktioniert einfach!

Nehmen Sie eine grundlegende Klassenhierarchie:

%Vor%

Und definieren Sie ein Merkmal:

%Vor%

Hinweis: Es ist beabsichtigt, dass das Merkmal für Derived nicht direkt true oder false angibt, sondern stattdessen das Verhalten von seinem Vorgänger übernimmt. Auf diese Weise folgt die gesamte Hierarchie automatisch, wenn der Vorgänger die Haltung ändert. Da die Basisfunktionalität meistens vom Vorfahren bereitgestellt wird, ist es sinnvoll, dem Merkmal zu folgen. Noch mehr für Typeigenschaften.

%Vor%

Hinweis : Die Verwendung von Ellipsen ist beabsichtigt, dies ist die Überlastungsübernahme. Eine Template-Funktion wäre eine bessere Übereinstimmung als die anderen Überladungen (keine Konvertierung erforderlich), während die Ellipse immer die schlechteste Übereinstimmung ist, die garantiert, dass sie nur diejenigen aufnimmt, für die keine andere Überladung geeignet ist.

Ich nehme an, es ist unnötig zu präzisieren, wie prägnanter der letztere Ansatz ist? Nicht nur, dass Sie die template <> Unordnung loswerden, erhalten Sie auch kostenlos Vererbung.

  

Kann enable_if so implementiert werden?

Ich denke leider nicht, aber wie ich schon sagte: das ist kein Merkmal. Und die std -Version funktioniert gut mit constexpr , weil sie ein bool -Argument und keinen Typ verwendet:)

  

Also Warum ?

Nun, der einzige technische Grund ist, dass ein guter Teil des Codes bereits auf einer Reihe von Merkmalen beruht, die in der Vergangenheit als Typen angegeben wurden ( std::numeric_limit ), so dass die Konsistenz dies diktieren würde.

Außerdem macht es die Migration von boost::is_* einfach so einfach!

Ich persönlich halte das für bedauerlich. Aber ich bin wahrscheinlich sehr viel eifriger dabei, den bestehenden Code zu überprüfen, den ich geschrieben habe, als die durchschnittliche Corporation.

    
Matthieu M. 17.01.2012 18:51
quelle
3

Ein Grund ist, dass der Vorschlag type_traits älter ist als der Vorschlag constexpr.

Ein anderer ist, dass Sie bei Bedarf eigene Spezialisierungen für Ihre eigenen Typen hinzufügen können.

    
Bo Persson 17.01.2012 14:54
quelle
2

Wahrscheinlich weil Boost bereits eine Version von type_traits hatte, die mit Templates implementiert wurde.

Und wir alle wissen, wie viel Leute im Standardisierungsgremium den Boost kopieren.

    
Lalaland 17.01.2012 14:50
quelle
2

Ich würde sagen, der Hauptgrund ist, dass type_traits bereits Teil von tr1 war und daher im Prinzip garantiert in mehr oder weniger derselben Form im Standard endet, also vor constexpr . Andere mögliche Gründe sind:

  • Wenn Sie die Merkmale als Typen verwenden, können Sie Funktionen über den Merkmalstyp überladen
  • Viele Merkmale (wie remove_pointer ) definieren type anstelle von value , also müssen sie auf diese Weise ausgedrückt werden. Verschiedene Schnittstellen für Merkmale zu haben, die Werte und Merkmale definieren, die Typen definieren, scheint nicht notwendig zu sein
  • templated structs kann teilweise spezialisiert sein, während Funktionen nicht können, so dass die Implementierung einiger Merkmale einfacher werden kann

Für Ihre zweite Frage: As enable_if definiert ein type (oder nicht, wenn es falsch übergeben wird) ist ein geschachtelter Typdef innerhalb eines struct wirklich der Weg zu gehen

    
Grizzly 17.01.2012 14:57
quelle