Variadic pack expansion und Arrays



  • Gibt es eine Möglichkeit, die folgenden Funktionen mithilfe von variadic templates nur einmal schreiben zu müssen, anstatt mit dem Präprozessor N Überladungen zu erzeugen wie in der Steinzeit?

    #include <cstddef>
    
    template <typename T, std::size_t N1>
        void foo(T (&&array)[N1])
    {
        // ...
    }
    template <typename T, std::size_t N1, std::size_t N2>
        void foo(T (&&array)[N1][N2])
    {
        // ...
    }
    // ...
    
    void bar(void)
    {
        foo<int>({ 1, 2, 3 }); // ruft foo<int, 3>() auf
        foo<int>({ { 1, 0 }, { 0, 1 } }); // ruft foo<int, 2, 2>() auf
    }
    

    Falls die pack expansion in multidimensionalen Arraysignaturen irgendwie erlaubt sein sollte, habe ich die Syntax noch nicht gefunden. Natürlich kann ich mir rekursiv einen Typen MultidimensionalArray<T, N...> definieren und den als Template-Argumenttyp verwenden, aber dann können die Templateparameter nicht mehr abgeleitet werden.



  • Hallo audacia,

    wenn dir eine rekursive Lösung gut genug ist:

    #include <cstddef>
    #include <type_traits>
    #include <iostream>
    
    template< typename T >
    std::ostream& foo( std::ostream& str, T const& value ) {
    	return str << value;
    }
    template< typename T, std::size_t N >
    std::ostream& foo( std::ostream& str, const T ( &array )[ N ] )
    {
    	str << "{ ";
    	if( N )
    		for( std::size_t i = 0;; ) {
    			foo( str, array[ i ] );
    			if( ++i == N )
    				break;
    			str << ", ";
    		}
    	str << " }";
    	return str;
    }
    
    int main()
    {
    	foo( std::cout, { 4, 2 } ) << '\n';
    	int x[ 2 ][ 1 ][ 3 ] = { { { 1, 2, 8 } }, { { 3, 7, 5 } } };
    	foo( std::cout, x ) << '\n';
    }
    

    Ausgabe:

    { 4, 2 }
    { { { 1, 2, 8 } }, { { 3, 7, 5 } } }
    

    Was anderes fällt mir auch nicht ein...

    LG


  • Mod

    audacia schrieb:

    Falls die pack expansion in multidimensionalen Arraysignaturen irgendwie erlaubt sein sollte, habe ich die Syntax noch nicht gefunden. Natürlich kann ich mir rekursiv einen Typen MultidimensionalArray<T, N...> definieren und den als Template-Argumenttyp verwenden, aber dann können die Templateparameter nicht mehr abgeleitet werden.

    Du könntest allerdings eine Templatemetafunktion schreiben, die einen generischen Typ (T&&) nimmt, und daraus die benötigten Informationen extrahiert.


  • Mod

    camper schrieb:

    audacia schrieb:

    Falls die pack expansion in multidimensionalen Arraysignaturen irgendwie erlaubt sein sollte, habe ich die Syntax noch nicht gefunden. Natürlich kann ich mir rekursiv einen Typen MultidimensionalArray<T, N...> definieren und den als Template-Argumenttyp verwenden, aber dann können die Templateparameter nicht mehr abgeleitet werden.

    Du könntest allerdings eine Templatemetafunktion schreiben, die einen generischen Typ (T&&) nimmt, und daraus die benötigten Informationen extrahiert.

    Und wie soll das die gezeigte Syntax unterstützen?


  • Mod

    Arcoth schrieb:

    camper schrieb:

    audacia schrieb:

    Falls die pack expansion in multidimensionalen Arraysignaturen irgendwie erlaubt sein sollte, habe ich die Syntax noch nicht gefunden. Natürlich kann ich mir rekursiv einen Typen MultidimensionalArray<T, N...> definieren und den als Template-Argumenttyp verwenden, aber dann können die Templateparameter nicht mehr abgeleitet werden.

    Du könntest allerdings eine Templatemetafunktion schreiben, die einen generischen Typ (T&&) nimmt, und daraus die benötigten Informationen extrahiert.

    Und wie soll das die gezeigte Syntax unterstützen?

    Würde es nicht. Weil dann beim Aufruf initializer_list aber keine Arrays entstehen würden.



  • @Fytch: Danke, das ist freilich clever, aber in meinem Fall hilft es nichts, weil ich ja gerade auf diese inline array definition-Syntax aus bin. Das geht nur, wenn der Funktionsargumenttyp wirklich ein Array in voller Dimension ist. Für einen anderen Fall hilft mir aber diese rekursive Variante.

    camper schrieb:

    Du könntest allerdings eine Templatemetafunktion schreiben, die einen generischen Typ (T&&) nimmt, und daraus die benötigten Informationen extrahiert.

    Genau, aber dann habe ich wieder ein initializer_list<> -Argument mit allen daraus sich ergebenden Nachteilen:

    - es gibt keinerlei Layoutgarantien (dagegen haben Arrays auf jeden Fall zusammenhängendes Layout)
    - außerdem ist eine verschachtelte initializer_list<> ein jagged array, d.h. es gibt unnötige Indirektionen zur Laufzeit, und außerdem bekomme ich keinen Compilerhinweis, wenn verschiedene Sublisten unterschiedlich lang sind
    - und schließlich kann ich die Arrayform nicht zur Compilezeit feststellen.

    Mein erster Entwurf basierte auf verschachtelten initializer_list<> s, aber aus diesen Gründen wollte ich davon weg. Dann fiel mir durch Zufall auf, daß rvalue-Arrays diese inline-Syntax ermöglichen. Und jetzt muß ich halt zurück in die Steinzeit 🤡


  • Mod

    audacia schrieb:

    in meinem Fall hilft es nichts, weil ich ja gerade auf diese inline array definition-Syntax aus bin.

    Sofern nur eine ähnliche Syntax gefordert wird, wäre so etwas möglich (C++17):

    #include <array>
    #include <iostream>
    #include <typeinfo>
    using namespace std;
    template <typename T>
    void foo(T&&) {
        cout << typeid(T).name() << '\n';
    }
    int main() {
        foo(array{array{1, 0}, array{0, 1}}); // std::array<std::array<int, 2>, 2>
    }
    

    bzw. (C++11):

    #include <array>
    #include <iostream>
    #include <typeinfo>
    using namespace std;
    template <typename T>
    void foo(T&&) {
        cout << typeid(T).name() << '\n';
    }
    template <typename T, typename... U>
    constexpr std::array<typename std::remove_reference<T>::type, 1 + sizeof...(U)> make_array(T&& e1, U&&... e) {
        return {std::forward<T>(e1), std::forward<U>(e)...};
    }
    int main() {
        foo(make_array(make_array(1, 0), make_array(0, 1))); // std::array<std::array<int, 2>, 2>
    }
    


  • Leider scheint die Syntax beim GCC (getestet mit v6.3, v7.1) recht volatil zu sein:

    void bar(void)
    {
        foo<double>({ 1., 2., 3 }); // wenn auch nur ein Literal nicht exakt den angegebenen Typ hat, findet g++ die Überladung nicht mehr
        foo<unsigned>({ { 1, 0 }, { 0, 1 } }); // dsgl.
    }
    

    Das ist natürlich etwas lästig. Jedoch haben Clang (v3.8, v4.0) und VC++ damit keine Probleme, solange die Literale konvertierbar sind.

    Wer hat da nun recht?


  • Mod

    Mir scheint, g++ könnte recht haben. Bei einer oberflächliche Suche habe ich jedenfalls http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html keine offenen Probleme bei gefunden, die auf diese Situation anzuwenden wären. Evtl. sollten noch die Bug-Tracker der Compiler durchsucht werden.

    Meine vorläufige Interpretation:

    N4659 schrieb:

    17.8.2.1 Deducing template arguments from a function call [temp.deduct.call]
    1 Template argument deduction is done by comparing each function template parameter type (call it P)
    that contains template-parameters that participate in template argument deduction with the type of the
    corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers
    from P gives std::initializer_list<P'> or P'[N] for some P' and N and the argument is a non-empty
    initializer list (11.6.4), then deduction is performed instead for each element of the initializer list, taking P'
    as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N
    is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer
    list argument causes the parameter to be considered a non-deduced context (17.8.2.5).

    [...]

    4 In general, the deduction process attempts to find template argument values that will make the deduced A
    identical to A (after the type A is transformed as described above). However, there are three cases that allow
    a difference:
    (4.1) — If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be
    more cv-qualified than the transformed A.
    [...]

    Bezogen auf

    template <typename T, std::size_t N1>
        void foo(T (&&array)[N1]);
    foo<double>({ 1., 2., 3 });
    

    Obwohl in P' (=T) kein Templateparameter enthalten ist, der einer Deduktion bedarf, ist der gesamte Funktionsparameter ein Kontext in dem deduziert werden muss (wir müssen ja N1 ermitteln). Und nachdem N1 ermittelt wurde (aus der Anzahl der Element der Initialisiererliste), wird nicht abgebrochen: es muss immer noch für jedes individuelle Element der Liste geprüft werden, ob es (im Wesentlichen, bis auf die üblichen minimalen Transformationen) mit dem "deduzierten" Element identisch ist - obwohl dieses "deduzierte" Element tatsächlich identisch mit dem explizit angegebenen Templateargument ist, und gar nicht vom jeweiligen Element in der Initialisiererliste abhängt.

    Wenn wir annehmen, dass das Ergebnis nicht beabsichtigt ist, würde ich den Defekt in dem Satz

    If removing references and cv-qualifiers
    from P gives std::initializer_list<P'> or P'[N] for some P' and N and the argument is a non-empty
    initializer list (11.6.4), then deduction is performed instead for each element of the initializer list, [...]

    verorten. Im ursprünglichen Text für C++11 konnte die Arraygröße noch nicht deduziert werden, und da war das ok so (den Fall P'[N] gab es nicht). Mit der Erweiterung auf Arrays wurde aber dann versäumt, die Deduktion für die einzelnen Elemente nur dann durchzuführen, wenn P' selbst abhängig ist (bei initializer_list<P'> ist das ja immer der Fall).
    Clang zieht sich möglicherweise darauf zurück, dass ja gleich der erste Satz sagt:

    Template argument deduction is done by comparing each function template parameter type (call it P)
    that contains template-parameters that participate in template argument deduction with the type of the
    corresponding argument of the call (call it A) as described below.

    und damit die Aufforderung, die Deduktion für jedes einzelne Element durchzuführen, mangels zu deduzierender Templateparamter ins Leere läuft.


Anmelden zum Antworten