C++ Template Spezialisierung

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2352
Registriert: 04.08.2004, 20:06
Kontaktdaten:

C++ Template Spezialisierung

Beitrag von Jonathan »

Hi,

sagen wir ich habe zwei Funktionen:

Code: Alles auswählen

template<typename t> t convert(string blub);
template<typename t> vec3<t> convert(string blub);

auto a = convert<float>("5");
auto b = convert<vec3<float>> ("5, 12, 3");
Jetzt ist der Aufruf a) doppeldeutig, denn es könnte ein vec3 oder eine einzelne Zahl gemeint sein. Wie muss ich die zweite Template-Deklaration ändern, damit der Compiler die Fälle unterscheiden kann? Geht sowas überhaupt?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Hannes
Beiträge: 36
Registriert: 11.06.2008, 06:04

Re: C++ Template Spezialisierung

Beitrag von Hannes »

Soweit ich weiß, müssen sich überladene Funktionen in C++, in mehr als nur unterschiedlicher Rückgabetyp, unterscheiden.

Edit: Mit templates kenne ich mich aber nicht so aus.
Zuletzt geändert von Hannes am 14.11.2020, 21:16, insgesamt 1-mal geändert.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: C++ Template Spezialisierung

Beitrag von Schrompf »

Nenn sie doch einfach anders.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: C++ Template Spezialisierung

Beitrag von Spiele Programmierer »

Wenn du Template-Spezialisierung verwenden würdest, dann würde es gehen. Du verwendest in dem Beispiel aber nicht Template-Spezialisierung sondern Überladung. Und Überladungen dürfen sich nicht nur im Rückgabetyp unterscheiden.

Echte Spezialisierung würde so aussehen, denn der Rückgabetyp darf sich zwischen Spezialisierungen unterscheiden, d.h. er kann nicht zur Deduktion des Template-Parameters herangezogen werden:

Code: Alles auswählen

template<typename t> t convert(string blub);
template<typename t> vec3<t> convert<vec3<t>>(string blub);
Leider funktioniert das aber auch nicht. C++ unterstützt nämlich keine partielle Funktionsspezialisierung.

Auf diese Limitierung bin ich persönlich auch schon öfter gestoßen. Es gibt verschiedene mehr oder weniger (aber eher mehr) hässliche Workarounds, die man dann einsetzen kann. Ein paar Vorschläge, die ich zur Zeit auch alle je nach Situation irgendwo verwende, sind Folgende:
  1. Du könntest den eigentlichen Code in eine Klasse packen, die du spezialisieren darfst. Also so:

    Code: Alles auswählen

    template<typename T>
    struct convertInternally
    {
        static T Do(string blub) { ... }
    }
    template<typename T>
    struct convertInternally<vec3<T>>
    {
        static vec3<T> Do(string blub) { ... }
    }
    template<typename T> T convert(string blub)
    {
        return convertInternally<T>::Do(blub);
    }
    
    Nachteil ist, dass es extrem hässlich ist. Vorteile sind, dass es sogar mit C++98 geht und jeder eigene Funktionen durch weitere Spezialisierungen hinzufügen kann.
    Wahlweise kann man auch convertInternally<T>::Do direkt aufrufen.
  2. Wenn du C++17 verwenden kannst, gibt es die Möglichkeit if constexpr zu verwenden. Also so:

    Code: Alles auswählen

    template<typename T> static constexpr bool IsVec3 = false;
    template<typename T> static constexpr bool IsVec3<vec3<T>> = true;
    template<typename T> T convert(string blub)
    {
        if constexpr(IsVec3<T>)
        { ... }
        else
        { ... }
    }
    
    Nachteile davon sind, dass es C++17 erfordert und du die Liste der Möglichkeiten nicht extern erweitern kannst. Vorteil ist, dass der Code einigermaßen leserlich ist und je nach Situation (z.B. wenn man die IsVec3-Variablen recyclen kann oder man mit std::is_same oder sonst wie über den Code-Pfad entscheidet) recht kompakt ist.
  3. Es gibt auch noch die Möglichkeit Überladungen zu verwenden. Allerdings musst du den Rückgabewert vorher zu einem Parameter machen. Also via einer Referenz oder std::optional plus eine Referenz oder so etwas. Die Überladung ist in diesem Fall dann auch nicht mehr "ambigous", da ein vec3<T> höhere Priorität hat als ein T-Parameter.
    Die Vorteile davon sind, dass es den simpelsten Code liefert, seit C++98 oder so funktioniert und man die Liste der Funktionen extern erweitern kann. Nachteil ist, dass man Ausgabeparameter braucht und niemand mag Ausgabeparameter. Außerdem kann es in komplizierteren Situationen schwierig werden, den Compiler davon zu überzeugen, dass die Überladung nicht mehrdeutig ist und du einfach eine ganz bestimmte Überladung aufrufen willst. Man kann sich dann zwar mit std::enable_if und so bemühen, aber das ist dann richtig hässlich.

    Eine weitere Untermöglichkeit von der Variante mit Überladungen wäre noch, dass du den Rückgabewert lässt aber dafür einen Dummy-Parameter hinzufügst, der einzig und allein den Typ angibt. Also so:

    Code: Alles auswählen

    template<typename> struct Dummy {};
    template<typename T> T convert(string blub, Dummy<T>);
    template<typename T> vec3<T> convert(string blub, Dummy<vec3<T>>);
    Anstatt eines Template-Parameters musst du dann natürlich ein Dummy<...> {} mit entsprechenden Typen übergeben.

    Gegen die Dummy- bzw. Ausgabeparameter kann man auch noch eine Hilfsfunktion machen, die einfach den Wert der eigentlichen Funktion als Rückgabewert und ohne Dummy zurückgibt. Das ist dann in dieser Beziehung ähnlich zu meinem ersten Vorschlag.
Wenn der Code nicht generisch sein muss, bleibt auch noch die Möglichkeit sie anders zu nennen, wie Schrompf sagt. Das ist sicherlich die einfachste Variante. Nur halt schlecht, wenn man mal generisch arbeiten will. (z.B. für eine templatisierte Methode, die einen Wert direkt aus einer Text-/XML-/Json-Datei lädt.)
Zuletzt geändert von Spiele Programmierer am 15.11.2020, 00:03, insgesamt 4-mal geändert.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: C++ Template Spezialisierung

Beitrag von Schrompf »

Stimmt schon, so richtig generisch ist es mit getrennten Namen nicht. Aber dafür lesbar, simpel, und Du gibst hier doch eh den Param explizit an. Wenn Du Dir vorstellst, dass Template-Args Teil des Namens werden, ist es quasi die Version mit getrenntem Namen, nur mit sehr kompliziertem Syntax.

SpieleProgrammierer hat eine super Aufstellung geschrieben. Ich wollte nur hinzufügen, dass die Option 3 allgemein Type Dispatch genannt wird. Du erzeugt mit nem Dummy-Parameter einfach eine Überladung, damit die Funktionssignatur für den Compiler eindeutig ist.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2352
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Template Spezialisierung

Beitrag von Jonathan »

Vielen Dank für die Antworten.
Ich habe gestern auch noch eingesehen, dass ein auto v = data.as_vec2() nicht schlimmer zu schreiben ist als ein auto v = data.as<vec2>() und ich damit sämtliche template Probleme los bin. Trotzdem ist es interessant zu sehen wie man es machen könnte, wenn man denn unbedingt will. Ich schätze wenn der Aufruf in einem generischen Algorithmus stattfindet könnte sich sowas schon lohnen, aber ansonsten ist es hier wohl wieder einmal besser komplizierte Features nur weil sie existieren und zwanghaft zu benutzen.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C++ Template Spezialisierung

Beitrag von Krishty »

Jonathan hat geschrieben: 15.11.2020, 11:17Ich schätze wenn der Aufruf in einem generischen Algorithmus stattfindet könnte sich sowas schon lohnen, aber ansonsten ist es hier wohl wieder einmal besser komplizierte Features nur weil sie existieren und zwanghaft zu benutzen.
Fun fact: Mit meinen Vektoren habe ich die Entscheidung ziemlich bereut als ich Code brauchte, der sowohl mit Vierervektoren (SSE) kompilierbar ist als auch mit Achtervektoren (AVX).

4 oder 8 als Template-Parameter zu übergeben wäre in dem Fall kein Problem gewesen, aber in generischem Code den Namen eines Funktionsaufrufs zu ersetzen geht nur über die bekannten Umwege.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten