Automatischer Konstruktoraufruf für template operator?



  • Hallo,

    folgender Code:

    template <typename T>
    struct A { 
    	T value = 0; 
    	A() {} 
    	A(T value) : value(value) {} 
    };
    
    template <typename T>
    A<T> operator+(A<T> const &lhs, A<T> const &rhs) { return A<T>(lhs.value + rhs.value); }
    
    int main()
    {
    	A<double> a1, a2;
    	A<double> b = a1 + a2; // Geht
    
    	b = a1 + 5.0; // Geht nicht, siehe unten
    }
    

    In Zeile 16 bekomme ich folgende Fehlermeldung des Compilers: error C2784: "A<T> operator +(const A<T> &,const A<T> &)": Vorlage-Argument für "const A<T> &" konnte nicht von "double" hergeleitet werden.

    Wenn ich jetzt A ohne template implementiere (also double als Typ hardcoded), funktioniert das allerdings problemlos.

    Wie kann ich das selbe mit Verwendung von templates erreichen?

    Was ich vermeiden will ist alle Kombinationsmöglichkeiten manuell ausschreiben zu müssen (also (A<T>, A<T>) , (A<T>, T) , (T, A<T>) , (T, T) ).

    Geht das auch mit templates?


  • Mod

    Warum einfach, wenn auch kompliziert geht?

    #include <utility>
    #include <type_traits>
    
    template <typename T>
    struct A {
        T value = 0;
        A() {}
        A(T value) : value(value) {}
    };
    
    template <typename>
    struct is_A : std::false_type {};
    template <typename T>
    struct is_A<A<T>> : std::true_type {};
    
    template <typename T>
    auto&& getValue(T&& x) noexcept {
        if constexpr (is_A<std::remove_cv_t<std::remove_reference_t<T>>>{})
            return std::forward<T>(x).value;
        else
            return std::forward<T>(x);
    }
    
    template <typename T, typename U, std::enable_if_t<(is_A<T>{} != is_A<U>{}) || (is_A<T>{} && std::is_same<T,U>{}),int> = 0>
    auto operator+(T const &lhs, U const &rhs) { return std::conditional_t<is_A<T>{},T,U>(getValue(lhs) + getValue(rhs)); }
    
    int main()
    {
        A<double> a1, a2;
        A<double> b = a1 + a2; // Geht
    
        b = a1 + 5.0; // Geht
    }
    


  • camper schrieb:

    Warum einfach, wenn auch kompliziert geht?

    Omg... ist das wirklich die einfachste Variante um das zu realisieren?

    Das ist schon ziemlich schwierig nachzuvollziehen was da passiert...

    Kann auch kein constexpr if benutzen (Compiler unterstützt das nicht), würde das theoretisch auch ohne gehen?



  • Es geht "einfacher" (leichter verständlich) indem der operator + Für T direkt "spezialisiert" wird.

    template <typename T>
    struct A {
        T value = 0;
        A() {}
        A(T value) : value(value) {}
    };
    
    template <typename T>
    A<T> operator+(A<T> const &lhs, A<T> const &rhs) { return A<T>(lhs.value + rhs.value); }
    
    template <typename T>
    A<T> operator+(A<T> const &lhs, T const &rhs) { return A<T>(lhs.value + rhs); }
    
    int main()
    {
        A<double> a1, a2;
        A<double> b = a1 + a2; // Geht
    
        b = a1 + 5.0; // Geht nicht, siehe unten
    }
    

    Dadurch ist es nicht mehr symetrisch

    A<double> a;
    A<double> b;
    b= 5.0 + a; // Geht nicht.
    

    Für die Symmetrie des + operators müsste man den operator jeweils für die linke und rechte seite zum operator für reines T spezialisieren:

    template <typename T>
    struct A {
        T value = 0;
        A() {}
        A(T value) : value(value) {}
    };
    
    template <typename T>
    A<T> operator+(A<T> const &lhs, A<T> const &rhs) { return A<T>(lhs.value + rhs.value); }
    
    template <typename T>
    A<T> operator+(A<T> const &lhs, T const &rhs) { return A<T>(lhs.value + rhs); }
    
    template <typename T>
    A<T> operator+(T const &lhs, A<T> const &rhs) { return A<T>(lhs + rhs.value); }
    
    int main()
    {
        A<double> a1, a2;
        A<double> b = a1 + a2; // Geht
    
        b = a1 + 5.0; 
        b = 5.0 + a1; 
    }
    


  • @camper: Wie lernt man am besten solchen Code zu schreiben?

    Ich kann sowas jeweils knapp nachvollziehen, koennte sowas aber nie selber schreiben. Das will ich aendern.



  • firefly schrieb:

    Es geht "einfacher" (leichter verständlich) indem der operator + Für T direkt "spezialisiert" wird.

    Das schon, aber wie geschrieben will ich genau das vermeiden:

    happystudent schrieb:

    Was ich vermeiden will ist alle Kombinationsmöglichkeiten manuell ausschreiben zu müssen (also (A<T>, A<T>) , (A<T>, T) , (T, A<T>) , (T, T) ).

    da das den Code schnell mit lauter redundanten Funktionen aufbläht...


  • Mod

    template <typename T>
    struct A { 
    	T value = 0; 
    	A() {} 
    	A(T value) : value(value) {} 
    	friend A operator+(A const &lhs, A const &rhs) { return A(lhs.value + rhs.value); }
    };
    
    int main()
    {
    	A<double> a1, a2;
    	A<double> b = a1 + a2; // Geht
    
    	b = a1 + 5.0; // Geht
    }
    

    sollte naheliegend sein. Etwas enttäuschend, dass das bisher nicht angeboten wurde.

    icarus2 schrieb:

    @camper: Wie lernt man am besten solchen Code zu schreiben

    Ich bin nicht sicher, was man da extra lernen müsste. Die Grundidee ist, nicht den gesamten Operator zu spezialisieren, sonden Spezialisierung nur bei den elementaren Operationen einzusetzen, bei denen ein echter Unterschied besteht (hier: den Wert aus T herauszuholen) und die Anwendung des resultierenden Templates durch ein entsprechendes Prädikat auf passende Templateargumente zu beschränken.
    Auf if constexpr kann selbstverständlich verzichtet werden, indem das entsprechende Template getValue passend überladen wird.



  • Wie wärs mit

    #include "collection/entities.hpp"
    
    template <typename T>
    struct A {
        T value = 0;
        A() {}
        A(T value) : value(value) {}
    };
    
    template <typename T>
    A<T> operator+(A<T> const &lhs, A<T> const &rhs) { return A<T>(lhs.value + rhs.value); }
    
    int main()
    {
        A<double> a1, a2;
        A<double> b = a1 + a2; // Geht
    
        b = ::operator+<double>(a1, 5.); // Geht nicht, siehe unten
    }
    


  • camper schrieb:

    sollte naheliegend sein. Etwas enttäuschend, dass das bisher nicht angeboten wurde.

    Das funktioniert tatsächlich perfekt und ist auch noch super einfach... aber warum geht das mit einem friend member Operator und nicht mit dem Operator als freie Funktion? Dachte immer da gibt es nicht wirklich einen Unterschied (außer Zugriff auf private Sachen)?

    wörtner schrieb:

    Wie wärs mit

    Ja... natürlich könnte man auch ::operator+<double>(a1, 5.) schreiben, aber meine Motivation hinter dem Ganzen ist, dass es gut/natürlich aussehen bzw. übersichtlich sein soll. Dann könnte man auch halt auch gleich a1 + A<double>(5.0) schreiben.


  • Mod

    Eleganter als die Zweckentfremdung von friend , ist ein verallgemeinertes Hilfstemplate im Stil von Boost.Operators. Ich habe in dieser Bibliothek nichts gefunden, was dem Trait entsprechen würde, vllt. könnte man in der mailing list eine Erweiterung vorschlagen.

    aber warum geht das mit einem friend member Operator und nicht mit dem Operator als freie Funktion?

    Das wäre wohl nur möglich, wenn template argument deduction for class templates auf Parameter ausgeweitet worden wäre, was aus guten Gründen nicht geschehen ist. Die Idee hinter dem friend Ansatz ist, die passende Deklaration ad-hoc zu generieren, wenn eine Spezialisierung instantiiert wird; Diese Deklaration benennt eine Funktion, die also implizite Konvertierungen der Argumente passend nach A zulässt.


  • Mod

    happystudent schrieb:

    aber warum geht das mit einem friend member Operator und nicht mit dem Operator als freie Funktion?

    operator+ als friend ist hier eine ganz normale Funktion, kein Funktionstemplate.

    Du kannst durchaus auch

    A<int> operator+(A<int> const &lhs, A<int> const &rhs) { return A(lhs.value + rhs.value); }
    A<double> operator+(A<double> const &lhs, A<double> const &rhs) { return A(lhs.value + rhs.value); }
    A<float> operator+(A<float> const &lhs, A<float> const &rhs) { return A(lhs.value + rhs.value); }
    // usw.
    

    schreiben. Es wird dir aber schwer fallen, auf diese Weise eine Operatorfunktion für jedes mögliche Templateargument zu definieren.



  • Ok verstehe... wieder was gelernt, Danke 👍


Anmelden zum Antworten