nested brace-enclosed initializer list



  • Hi,

    ich versuche egrade eine Nd-Vektorklasse zur Uebung zu implementieren, nachdem wir jetzt endlich bei uns den Stadanrd gewechselt haben 👍.

    Die Anzahl der elemente ist fix zur compile zeit und
    ich versuche gerade einen Konstruktor zu schreiben, der die Klasse mithilfe von exakt N elementen initialisiert. Mein Versuch sieht wie folgt aus:

    template<class T, std::size_t N>
    class vectorN{
    public:
        //initialize from N 
        template<class... Init>
        vectorN(Init&&... init):m_storage{{std::forward<Init>(init)...}}{
    	static_assert(sizeof...(Init) == N, "wrong number of arguments");
    	}
    private:
        std::array<T,N> m_array;
    };
    

    Das funktioniert leider nicht ganz richtig.

    vectorN<double,3>  v = {2,2.0,3.0};
    //resultat: warning: narrowing conversion of 'std::forward<int>((* & init#0))' from 'int' to 'double' inside { } [-Wnarrowing]
    

    Wenn ich als argument jetzt aber einen std::complex verwende und auch ueber braced initialization versuche zu initialisieren, schlaegt das fehl:

    vectorN<std::complex<double>,3>  v2 = {{2,2},2.0,3.0};
    //error: could not convert '{{2, 2}, 2.0e+0, 3.0e+0}' from '<brace-enclosed initializer list>' to 'vectorN<std::complex<double> >'
    

    in prinzip sollte das aber gehen, weil es mit normalen arrays geht:

    std::complex<double>  v3[] = {{2,2},2.0,3.0};
    

    aber wie mache ich es richtig?


  • Mod

    Wenn ich das richtig sehe, sollen Konvertierungen und Braceinitialisierung für einzelne Elemente möglich sein.
    Dann in die Verwendung eines Templatekonstruktors nicht angezeigt, weil Konvertierungen auf Funktionsargumente, die für Templateparameterdeduktion herangezogen werden, unterdrückt sind (und {...}-Ausdrücke werden zu initializer_list<foo>).
    Was gebraucht wird, ist ein Parameterpack mit den richtigen Typen, dass nicht zum Konstruktor selbst gehört, sondern zum umschließenden Template.
    z.B. so:

    template <typename T, typename... U>
    struct first {
        using type = T;
    };
    
    template <typename... T>
    struct vector_base {
        vector_base(T&&... init) : m_array{{std::move(init)...}} {}
        std::array<typename first<T...>::type,sizeof...(T)> m_array;
    };
    

    Jetzt muss nur noch vectorN die richtige Basis berechnen und den Konstruktor erben:

    template <typename T, size_t N, typename U = vector_base<>>
    struct make_vector_base;
    template <typename T, size_t N, typename... U>
    struct make_vector_base<T, N, vector_base<U...>>
        : make_vector_base<T, N-1, vector_base<T, U...>>
    {};
    template <typename T, typename... U>
    struct make_vector_base<T, 0, vector_base<U...>> {
        using type = vector_base<U...>;
    };
    
    template<class T, std::size_t N>
    class vectorN : private make_vector_base<T, N>::type {
        using base = typename make_vector_base<T, N>::type;
    public:
        //initialize from N
        using base::base;
    };
    


  • Ah klasse. Wenn ich das also richtig verstehe, erzeugt der code einfach einen Konstruktor mit N Argumenten vom Typ T. Diese kann der Compiler dann finden und da er dann den exakten Typ kennt, kann er herausfinden, welche Konvertierung für die inneren {} gebraucht wird. Trickreich. Ich werde mal probieren ob ich das analog für Matrizen hinkriege



  • camper schrieb:

    Templatekonstruktors

    😡


  • Mod

    otze schrieb:

    Ah klasse. Wenn ich das also richtig verstehe, erzeugt der code einfach einen Konstruktor mit N Argumenten vom Typ T.

    Jau. Geht auch IMO eleganter, siehe e.g. meine Antwort hier. Da du ja nun C++14 verwenden kannst, kann man übertragen:

    // Helfer:
    template <std::size_t, typename T>
    using ignore_val = T;
    
    template<class T, std::size_t N, typename=std::make_index_sequence<N>>
    class vectorN;
    
    template<class T, std::size_t N, std::size_t... I>
    class vectorN<T, N, std::index_sequence<I...>> {
    public:
        vectorN(ignore_val<I, T&&>... args) : arr{std::move(args)...} {}
    
        // [..]
    };
    

    So kannst du noch weitere pack expansions im Code der Klasse haben. Der Basisklassen-Variante stellt ja lediglich Konstruktoren bereit. Ihr Vorteil (der in der vorigen Antwort nicht demonstriert wurde), ist, dass in der Rekursion auch alle Konstruktorvarianten mit weniger Parametern eingeführt werden können (zwar hier nicht gefragt, aber eben eine Möglichkeit).

    Edit: Mir fällt gerade auf, dass du wahrscheinlich eben nur C++11 hast (Progress in dieser Richtung bei gewöhnlichen Arbeitgebern ist wahrscheinlich wahnsinnig langsam?). Funktioniert natürlich immer noch, nur dass du die Komponenten selber implementieren musst. Was gut ist, weil man lernt.


  • Mod

    Arcoth schrieb:

    Geht auch IMO eleganter, siehe e.g. meine Antwort hier. Da du ja nun C++14 verwenden kannst, kann man übertragen:

    ...
    

    So kannst du noch weitere pack expansions im Code der Klasse haben. Der Basisklassen-Variante stellt ja lediglich Konstruktoren bereit. Ihr Vorteil (der in der vorigen Antwort nicht demonstriert wurde), ist, dass in der Rekursion auch alle Konstruktorvarianten mit weniger Parametern eingeführt werden können (zwar hier nicht gefragt, aber eben eine Möglichkeit).

    Das ist eine Möglichkeit. Der Grund, weshalb ich hier den Weg über die Basisklasse gegenagen bin, ist der, dass so keine Änderung an den Templateparametern von vectorN nötig ist (siehe auch die Diskussion in Hinblick auf zusätzliche Templateparameter von Klassentemplates der Standardbibliothek, was ja nicht mehr zulässig ist).
    Die Grundidee ist die Verwendung eines Parameterpacks, das nicht unmittelbar zum Funktionstemplate gehört (und somit nicht erst deduziert werden muss). Der Rest ist Implementationsdetail.

    schablonenmann schrieb:

    camper schrieb:

    Templatekonstruktors

    😡

    Einwände? Wir sind hier noch nicht soweit, dass solitäre Smileys die Verwendung verbaler Sprache überflüssig machen. (Anm. ich hätte hier auch Konstruktortemplate schreiben können - Verwendung des einen impliziert die Verwendung des anderen).


  • Mod

    Die Grundidee ist die Verwendung eines Parameterpacks, das nicht unmittelbar zum Funktionstemplate gehört (und somit nicht erst deduziert werden muss).

    Ich habe den Eindruck, es fehlt ein Sprachfeature, dass Packs ad hoc generieren kann. Gibt es irgendwelche Paper in der Richtung? Ich habe eine grobe Vorstellung eines Designs dafür, wenn ich nichts finde werde ich mal was draften und in std-proposals posten.

    Einwände?

    Es hört sich merkwürdig an, so wie sich Schablonenkonstruktor merkwürdig anhört. Konstruktorschablone wäre schon besser. Hätte mich aber nicht genug gekümmert, um es zu korrigieren.


  • Mod

    Arcoth schrieb:

    Die Grundidee ist die Verwendung eines Parameterpacks, das nicht unmittelbar zum Funktionstemplate gehört (und somit nicht erst deduziert werden muss).

    Ich habe den Eindruck, es fehlt ein Sprachfeature, dass Packs ad hoc generieren kann. Gibt es irgendwelche Paper in der Richtung? Ich habe eine grobe Vorstellung eines Designs dafür, wenn ich nichts finde werde ich mal was draften und in std-proposals posten.

    Ich stelle mir das ungefähr so for:

    using... foo = int, char, double;
    using... bar = foo..., foo...;
    template <typename T>
    using... baz = T, T;
    //Problem:
    using... x = baz<foo>...; // ??
    

  • Mod

    camper schrieb:

    Arcoth schrieb:

    Die Grundidee ist die Verwendung eines Parameterpacks, das nicht unmittelbar zum Funktionstemplate gehört (und somit nicht erst deduziert werden muss).

    Ich habe den Eindruck, es fehlt ein Sprachfeature, dass Packs ad hoc generieren kann. Gibt es irgendwelche Paper in der Richtung? Ich habe eine grobe Vorstellung eines Designs dafür, wenn ich nichts finde werde ich mal was draften und in std-proposals posten.

    Ich stelle mir das ungefähr so for:

    using... foo = int, char, double;
    using... bar = foo..., foo...;
    template <typename T>:
    using... baz = T, T;
    //Problem:
    using... x = baz<foo>...; // ??
    

    Ein Problem ist, dass packs verschiedene Arten von Elementen haben können; Typen, Objekte und Templates. Innerhalb eines Templates sollte bei der Deklaration eines solchen "statischen" packs seine Art angeben werden.

    Ich wäre eher für

    typename... foo = {int, char, double};
    int... bar{0, 1, 2, 3}; // = optional?
    template <typename> class... foobar = {/* … */};
    

    Ich denke nicht, dass, analog zu :: template , ein so eingeführtes pack gekennzeichnet werden muss.

    Zu dem Problem:

    using... x = baz<foo>...; // ??
    

    Das muss definitiv ill-formed sein. Mein Vorschlag ist es, zu verbieten, pack templates mit packs zu instantiieren.



  • Arcoth schrieb:

    otze schrieb:

    Ah klasse. Wenn ich das also richtig verstehe, erzeugt der code einfach einen Konstruktor mit N Argumenten vom Typ T.

    Jau. Geht auch IMO eleganter, siehe e.g. meine Antwort hier. Da du ja nun C++14 verwenden kannst, kann man übertragen

    Nein, nur C++11, aber wir haben eine log(N) implementation von integer_sequence bei uns in der toolbox. Das Problem sind auch nicht die Templateparameter, sondern dass die Fehlermeldungen durch so einen Typen beliebig nervig werden koennen. Da ist mir der Basisklassenansatz schon lieber.

    ---------
    Ich habe das mit der {}-initialisierung noch immer nicht ganz verstanden, die Regeln sind fuer mich noch nicht ganz durchdringbar. Warum z.B. kann ich keinen Konstruktor haben der ein std::array<T,N> nimmt und den man mit den braces konstruieren kann? Das waehre imho die huebscheste Loesung.


  • Mod

    otze schrieb:

    Ich habe das mit der {}-initialisierung noch immer nicht ganz verstanden, die Regeln sind fuer mich noch nicht ganz durchdringbar. Warum z.B. kann ich keinen Konstruktor haben der ein std::array<T,N> nimmt und den man mit den braces konstruieren kann? Das waehre imho die huebscheste Loesung.

    Natürlich ist das möglich. Nur kannst du dann nicht mittels () initialisieren, sondern nur über {{}} oder ({}) . Nicht sonderlich hübsch.


Anmelden zum Antworten