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.


Log in to reply