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:
- 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.
- 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.
- 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.)