template Argument im Konstruktor



  • Nein anders.

    Du willst ja eigentlich ein Datum speichern, aber bei der Objekterzeugung möchtest du auch alle Datentypen reingeben können, die implizit zu deinem Typ gecastet werden können:

    template<class T>
    struct Test
    {
    	T val;
    
    	template<class P>
    	Test(P x) : val(x)
    	{}
    };
    
    struct Foo
    {};
    
    struct Bar
    {};
    
    int main()
    {
    	Test<long> t1{5}; // OK
    	Test<double> t2{3.1415f}; // OK
    
    	//Test<Foo> t3{Bar{}}; // Fail
    
    	return 0;
    }
    

    Nachtrag: Alles was nicht impliziet castbar ist, wirft einen Fehler (wie die auskommentierte Zeile zeigt). Alles implizit castbare klappt anstandslos.



  • template <typename T, typename T2> struct test
    {
        T2 val;
    
        explicit test(T2 val) : val(val)
        {
        }
    };
    
    template <typename T, typename T2>
    test<T,T2> make_test(T2 val)
    {
        return {val};
    }
    
    auto t = make_test<int>(2.5);
    


  • Skym0sh0 schrieb:

    Nachtrag: Alles was nicht impliziet castbar ist, wirft einen Fehler (wie die auskommentierte Zeile zeigt). Alles implizit castbare klappt anstandslos.

    Hm ja, also das sollte halt auch mit nicht cast-baren Typen klappen...

    make<auto> schrieb:

    template <typename T, typename T2> struct test
    {
        T2 val;
     
        explicit test(T2 val) : val(val)
        {
        }
    };
    
    template <typename T, typename T2>
    test<T,T2> make_test(T2 val)
    {
        return {val};
    }
    
    auto t = make_test<int>(2.5);
    

    Also das klappt schonmal... Wäre zwar schöner wenn sich der Compiler das selbst zusammen reimen könnte, aber dann halt so^^



  • Das kommt halt drauf an, was du vor hast.

    Wenn du wirklich einen weiteren Typ als Member halten willst, dann musst du diesen natürlich (und da führt kein Weg dran vorbei) in der Template Parameter Liste (wie heisst das Ding eig?) aufführen.

    Mich persönlich nervts auch manchmal, dass man bei der Instanziierung eines Klassentemplates alle Typen angeben muss, aber dafür nutzt man heute erzeugende FUnktionen und auto. Also so wie make<auto> es gezeigt hat...


  • Mod

    @make<auto>: Dein Code ist falsch. copy-list-initialization ist implizit, der Konstruktor ist aber explicit .

    Edit: @happy: So muss es aussehen:

    template <typename T, typename T2>
    test<T,T2> make_test(T2 val)
    {
        return test<T, T2>{ val };
    }
    


  • happystudent schrieb:

    template <typename T>
    struct test
    {
    	T2 val; // <- Problem
    
    	template <typename T2> explicit test(T2 val_) : val(val_)
    	{
    
    	 }
    };
    

    Finde das ganze Unterfangen fragwürdig. Wenn du mal genau drüber anchdenkst macht das garkein Sinn was da steht. Entweder du hast cast-bare Typen, dann was Skym0sh0 schrieb. Oder, du hast einen von T ableitbaren Typen, dann Traits:

    template <typename T, typename Traits=default_traits<T>>
    struct test
    {
            typedef typename Traits::t2_type t2_type;
    	t2_type val; // <- kein Problem
    
    	explicit test(t2_type val_) : val(val_)
    	{
    
    	}
    };
    

    Oder dein Type hängt schlicht und einfach von zwei Parametern ab.



  • ScottZhang schrieb:

    Finde das ganze Unterfangen fragwürdig. Wenn du mal genau drüber anchdenkst macht das garkein Sinn was da steht. Entweder du hast cast-bare Typen, dann was Skym0sh0 schrieb. Oder, du hast einen von T ableitbaren Typen, dann Traits:

    template <typename T, typename Traits=default_traits<T>>
    struct test
    {
            typedef typename Traits::t2_type t2_type;
    	t2_type val; // <- kein Problem
    
    	explicit test(t2_type val_) : val(val_)
    	{
    
    	}
    };
    

    Oder dein Type hängt schlicht und einfach von zwei Parametern ab.

    Was ist bitte default_traits<T> ?

    Das existiert bei mir nicht, auch nicht nach includen von <type_traits> und in der Doku wird es auch nirgends aufgeführt. Bei Google finde ich mittels "C++ default_trait" auch nichts...

    Und wieso soll das Unterfangen "fragwürdig" sein? Template Funktionen können sich ja auch selber die jeweiligen Typen erschließen, wäre nur konsistent wenn Klassen das auch könnten.



  • Das klingt deshalb fragwürdig, weil dein Klassen so vom jeweiligen Aufruf des Konstruktors abhängt (nicht der Zustand sonder die Klasse selbst).

    Mit default_traits meine ich einfach nur deine Abhängigkeit des Types T2 von T. Das müsstest du selber natürlich noch vorgeben. Du kannst das auch fest in die Klasse verbasteln ohne zweiten Templateparameter.



  • ScottZhang schrieb:

    Das klingt deshalb fragwürdig, weil dein Klassen so vom jeweiligen Aufruf des Konstruktors abhängt (nicht der Zustand sonder die Klasse selbst).

    Ja und? Ganz ehrlich, wo ist dabei jetzt das Problem, bei template Funktionen funktioniert es ja ganz genauso?

    ScottZhang schrieb:

    Mit default_traits meine ich einfach nur deine Abhängigkeit des Types T2 von T. Das müsstest du selber natürlich noch vorgeben. Du kannst das auch fest in die Klasse verbasteln ohne zweiten Templateparameter.

    Aber ein Typ T ist ja nicht unbedingt abhängig von T2 bzw. soll explizit nicht abhängig davon sein. Wenn ich etwas wie std::tuple etwa habe:

    // std::tuple<T1, T2, T3, T4>
    std::tuple<int, const char*, double, char> t(1, "test", 2.5, 'c');
    

    Wieso sollte hier T2 (ein string) auch nur irgendwie mit T1 (einem int) zusammenhängen? Dabei ist die kilometerlange template-Argument Liste doch völlig unnötig und redundant, da durch die Argumente des Konstruktors bereits festgelegt ist welchen Typ T1 bis T4 haben sollen. Viel schöner wäre doch:

    // std::tuple<T1, T2, T3, T4>
    std::tuple t(1, "test", 2.5, 'c');
    

    Mit auto und einer make_xyz Funktion kommt man dem schon recht nahe, das ist ja letztendlich auch nur ein Workaround. Besser wärs doch wenn das direkt von der Sprache unterstützt werden würde, oder?



  • happystudent schrieb:

    Ja und? Ganz ehrlich, wo ist dabei jetzt das Problem, bei template Funktionen funktioniert es ja ganz genauso?

    Und wo genau ist der Unterschied zwischen einem Klassentemplate und einem Funktionstemplate? Achja...

    happystudent schrieb:

    Mit auto und einer make_xyz Funktion kommt man dem schon recht nahe, das ist ja letztendlich auch nur ein Workaround. Besser wärs doch wenn das direkt von der Sprache unterstützt werden würde, oder?

    Syntax sugar in die Sprache integrieren?

    Und überhaupt, wenn die Klassentemplate-Argumente vom Konstruktor deduziert werden sollen:

    template<typename T>
    struct Foo
    {
    	Foo(T);
    	Foo(int);
    };
    Foo Bar(42); // Was hier?
    // Fehler nur bei int oder bei allen Typen? Was ist mit in int konvertierbare Typen?
    //------------------------
    template<typename T>
    struct Foo
    {
    	Foo(T);
    };
    template<>
    struct Foo<int>
    {
    };
    Foo Bar(42); // Was hier? Spezialisierungen pauschal ausschliessen?
    //------------------------
    template<typename T>
    struct Foo
    {
    };
    template<>
    struct Foo<int>
    {
    	Foo(int);
    };
    Foo Bar(42); // Was hier?
    //------------------------
    template<typename T>
    struct Foo
    {
    	Foo(T)
    };
    template<>
    struct Foo<int>
    {
    	Foo(int);
    };
    Foo Bar(42); // Oh nein, ich wurde zu Unrecht ausgeschlossen...
    //------------------------
    template<typename T>
    struct Foo
    {
    	template<typename U>
    	Foo(U);
    	Foo(T);
    };
    Foo Bar(42); // Was hier?
    

    Für mich sieht das alles tödlich inkonsistent aus.



  • Ok, wenn der Konstruktor auch noch templates hat, dann würde das nicht funktionieren, das stimmt. Daran hatte ich nicht gedacht 👍



  • happystudent schrieb:

    template <typename T>
    struct test
    {
    	T2 val; // <- Problem
    
    	template <typename T2> explicit test(T2 val_) : val(val_)
    	{
    
    	}
    };
    

    Ich versuche mich mal an einer Erklärung.

    Man stelle sich einmal vor, jemand schreibt eine Funktion:

    void foo(const test<int>& f) {
      // irgendwas
    }
    

    Welchen Typ hat f.val jetzt? Die Funktion ist ja explizit keine Template-Funktion, sondern arbeitet mit dem eigentlich voll qualifizierten Typ test<int> .

    Ein Aufruf könnte jetzt aber so erfolgen:

    foo(test<int>("Hi"));
    foo(test<int>(17));
    

    Im ersten Fall müsste f.val vom Typ const char* sein aber im 2. Fall ein int . Und das obwohl doch foo selbst keine Template-Funktion ist. Also kann das nicht funktionieren.



  • happystudent schrieb:

    ScottZhang schrieb:

    Das klingt deshalb fragwürdig, weil dein Klassen so vom jeweiligen Aufruf des Konstruktors abhängt (nicht der Zustand sonder die Klasse selbst).

    Ja und? Ganz ehrlich, wo ist dabei jetzt das Problem, bei template Funktionen funktioniert es ja ganz genauso?

    Der Type des Objekt muss vollständig sein bevor man damit irgendwas macht. Ich mein wie stellst du dir das vor? Du erzeugts zwei Objekte vom gleichen Type und anch dem Aufruf des Konstruktors haben sie nicht mehr den gleichen Type, hää?
    Denk doch mal nach.

    happystudent schrieb:

    ScottZhang schrieb:

    Mit default_traits meine ich einfach nur deine Abhängigkeit des Types T2 von T. Das müsstest du selber natürlich noch vorgeben. Du kannst das auch fest in die Klasse verbasteln ohne zweiten Templateparameter.

    Aber ein Typ T ist ja nicht unbedingt abhängig von T2 bzw. soll explizit nicht abhängig davon sein. Wenn ich etwas wie std::tuple etwa habe:

    // std::tuple<T1, T2, T3, T4>
    std::tuple<int, const char*, double, char> t(1, "test", 2.5, 'c');
    

    Wieso sollte hier T2 (ein string) auch nur irgendwie mit T1 (einem int) zusammenhängen? Dabei ist die kilometerlange template-Argument Liste doch völlig unnötig und redundant, da durch die Argumente des Konstruktors bereits festgelegt ist welchen Typ T1 bis T4 haben sollen. Viel schöner wäre doch:

    // std::tuple<T1, T2, T3, T4>
    std::tuple t(1, "test", 2.5, 'c');
    

    Mit auto und einer make_xyz Funktion kommt man dem schon recht nahe, das ist ja letztendlich auch nur ein Workaround. Besser wärs doch wenn das direkt von der Sprache unterstützt werden würde, oder?

    Ganz ehrlich, über die Abhängigkeiten deiner Typen musst du dir schon selbst im klaren sein, nicht wir. Bei Tupel hast du auch N Parameter für den Typen. Das wolltes du ja gerade nicht. Also muss sich T2 ja wohl irgendwie aus T ergeben oder nicht. Ansonsten wäre ja doch wieder die explizite Abhängigkeit deienr Klasse von T2 gegeben. Ne Hilfsfunktion wie make_tupel, die die Typededuktion nutzt um die Tipparbeit zu sparen kannst du dir auch ganz leicht schreiben.

    happystudent schrieb:

    Mit auto und einer make_xyz Funktion kommt man dem schon recht nahe, das ist ja letztendlich auch nur ein Workaround.

    Das ist kein Workaround. Ein Workaround für was denn?


Anmelden zum Antworten