campers TMP-Aufgabe(n): 7



  • Sone, wenn dir langweilig ist - ich hätte eine sogar sinnvolle TMP-Aufgabe für dich. :p
    Da ich so ne TMP-Niete bin kann ich nicht einschätzen ob dich das grob unterfordert oder nicht. Also:

    - Eine Liste von ints, die sortiert vorliegen. (Schön wäre es wenn ein static_assert feuert falls die Liste nicht sortiert ist) (Oder die Liste gleich per TMP-Quicksort sortieren? :p )
    - Die Liste bietet eine contains(int) Funktion an, die true zurück gibt wenn die gesuchte Zahl in der Liste enthalten ist. (Die gesuchte Zahl ist keine Compilezeit-Konstante)
    - Es wird nicht linear gesucht sondern per binärer Suche.
    - Die ganze binäre Suche ist über Template-Rekursion implementiert, dh. ohne for etc.

    Bsp:

    assert(search_list<1, 3, 12, 88, 177>::contains(7) == false);
    assert(search_list<1, 3, 12, 88, 177>::contains(177) == true);
    

    Vermutlich wird man die search_list in 2 listen aufsplitten und auf die beiden contains() aufrufen.

    Das wäre unglaublich praktisch um mit minimalem Aufwand prüfen zu können ob ein Wert ein gültiger enum Wert ist oder nicht. Gibt sicher noch mehr nette Einsatzgebiete dafür...



  • Ethon schrieb:

    Sone, wenn dir langweilig ist - ich hätte eine sogar sinnvolle TMP-Aufgabe für dich. :p

    Ich lese gerade "Standard C++ IOStreams and Locales". 😉

    - Die ganze binäre Suche ist über Template-Rekursion implementiert, dh. ohne for etc.

    Das ist unmöglich. Wenn der Wert erst zur Laufzeit feststeht, muss auch mit Methoden zur Laufzeit gearbeitet werden. Was spricht gegen std::binary_search ?
    Ich würde da mit

    template<int first, int second, int ... args>
    struct is_sorted : std::integral_constant<bool, is_sorted<first, second>::value && is_sorted<second, args...>::value> {};
    template<int first, int second>
    struct is_sorted<first, second> : std::integral_constant<bool, (first <= second)> {};
    

    prüfen und

    private:
        bool contains( int val, std::true_type )
        {
            // ...
        }
    
        bool contains( int val, std::false_type )
        {
            // ...
        }
    
    public:
        bool contains( int val )
        {
            contains( val, std::integral_constant<bool, is_sorted<args...>::value> );
        }
    

    Bei index_list einfügen.


  • Mod

    - Die ganze binäre Suche ist über Template-Rekursion implementiert, dh. ohne for etc.

    Gemeint ist, dass (per Templaterekursion) eine Funktion zu implementieren ist, die zur Laufzeit prüft, ob ein erst dann bekannter Wert in einem bereits beim Compilieren vorliegenden Set vorkommen. Das ist selbstverständlich möglich. Das Splitten von Listen war, glaube ich, auch eine von meinen Aufgaben.

    template<int first, int second, int ... args>
    struct is_sorted : std::integral_constant<bool, is_sorted<first, second>::value && is_sorted<second, args...>::value> {};
    template<int first, int second>
    struct is_sorted<first, second> : std::integral_constant<bool, (first <= second)> {};
    

    Das geht auch ohne Rekursion (analog zu all_same)

    Hast du mal gemessen, wie viel Zeit und Speicher dein Compiler für die partial_sum-Lösung benöigt ?



  • Ist das etwa so gemeint?

    #include <cstddef>
    #include <iostream>
    
    template<int... numbers>            struct is_sorted                { static bool const value = true; }; // f. leere Liste
    template<int x>                     struct is_sorted<x>             { static bool const value = true; };
    template<int x, int y, int... tail> struct is_sorted<x, y, tail...> { static bool const value = x <= y && is_sorted<y, tail...>::value; };
    
    template<template<int...> class list_template, typename so_far, int next> struct append_to_list;
    template<template<int...> class list_template, int next, int... so_far>
    struct append_to_list<list_template, list_template<so_far...>, next> {
      typedef list_template<so_far..., next> type;
    };
    
    template<template<int...> class list_template, typename so_far, std::size_t to_go, int next, int... tail>
    struct sublist_front_builder {
      static_assert(to_go <= sizeof...(tail) + 1, "Liste nicht lang genug.");
      typedef typename sublist_front_builder<list_template,
                                             typename append_to_list<list_template, so_far, next>::type,
                                             to_go - 1,
                                             tail...>::type type;
    };
    
    template<template<int...> class list_template, typename so_far, int next, int... tail>
    struct sublist_front_builder<list_template, so_far, static_cast<std::size_t>(0), next, tail...>
    {
      typedef so_far type;
    };
    
    template<template<int...> class list_template, std::size_t pos, int next, int... tail>
    struct sublist_back_builder
    {
      static_assert(pos <= sizeof...(tail) + 1, "Liste nicht lang genug.");
      typedef typename sublist_back_builder<list_template, pos - 1, tail...>::type type;
      static int const front = sublist_back_builder<list_template, pos - 1, tail...>::front;
    };
    
    template<template<int...> class list_template, int next, int... tail>
    struct sublist_back_builder<list_template, 0, next, tail...>
    {
      static int const front = next;
      typedef list_template<next, tail...> type;
    };
    
    template<template<int...> class list_template, int... numbers>
    struct bisect_list {
      static_assert(sizeof...(numbers) != 0, "Liste leer.");
    
      typedef typename sublist_front_builder<list_template, list_template<>, sizeof...(numbers) / 2, numbers...>::type first;
      typedef typename sublist_back_builder <list_template,                  sizeof...(numbers) / 2, numbers...>::type second;
    
      static int const middle = sublist_back_builder<list_template,         sizeof...(numbers) / 2, numbers...>::front;
    };
    
    template<int... numbers>     struct search_list             {
      static_assert(is_sorted<numbers...>::value, "Liste nicht sortiert.");
    
      static constexpr bool contains(int num) {
        typedef bisect_list<search_list, numbers...> bisect;
    
        return num < bisect::middle ? bisect::first::contains(num) : bisect::second::contains(num);
      }
    };
    
    template<>      struct search_list<>           { static constexpr bool contains(int    ) { return false; } };
    template<int x> struct search_list<x>          { static constexpr bool contains(int num) { return num == x; } };
    
    int main() {
      std::cout << search_list<2, 3, 5, 7, 11>::contains(7) << '\n'
                << search_list<2, 3, 5, 7, 11>::contains(9) << '\n'
                << search_list<2, 3, 5, 7, 11>::contains(5) << '\n'
        ;
    
      for(int i = 0; i < 20; ++i) {
        std::cout << i << ": " << search_list<2, 3, 5, 7, 11, 13, 17, 19>::contains(i) << '\n';
      }
    
      return 0;
    }
    

    Das ist jetzt erstmal nur so hingekladdet, also kann man da bestimmt noch Dinge dran verschönern. Und es ist nicht ausgiebig getestet.



  • Edit: seldon hat schon was gepostet... bloß nicht meins, bloß nicht meins... 😃

    Hast du mal gemessen, wie viel Zeit und Speicher dein Compiler für die partial_sum-Lösung benöigt ?

    Wahrscheinlich viel mehr... 🤡

    camper schrieb:

    template<int first, int second, int ... args>
    struct is_sorted : std::integral_constant<bool, is_sorted<first, second>::value && is_sorted<second, args...>::value> {};
    template<int first, int second>
    struct is_sorted<first, second> : std::integral_constant<bool, (first <= second)> {};
    

    Das geht auch ohne Rekursion (analog zu all_same)

    #include <type_traits>
    
    template<int...> struct index_list;
    
    template <typename... T> struct all_same;
    template <> struct all_same<>
        : std::true_type {};
    template <typename T, typename... U> struct all_same<T, U...>
        : std::is_same<all_same<T, U...>, all_same<U..., T>> {};
    
    template<typename, typename>
    struct is_sorted_impl;
    template<int ... args1, int ... args2>
    struct is_sorted_impl<index_list<args1...>, index_list<args2...>> : all_same<std::integral_constant<bool, (args1 <= args2)>...> {};
    
    #include <limits>
    
    template<int ... args> struct is_sorted;
    template<> struct is_sorted<> : std::true_type {};
    template<int first, int ... args>
    struct is_sorted<first, args...> : is_sorted_impl<index_list<first, args...>, index_list<args..., std::numeric_limits<int>::max()>> {};
    

    ?

    Das Splitten von Listen war, glaube ich, auch eine von meinen Aufgaben.

    Die erste, ja.
    Aber was soll das bringen?:

    #include <type_traits>
    
    typedef unsigned int_type;
    
    template<int_type ... args>
    struct index_list
    {
        typedef index_list identity;
        static constexpr auto length = sizeof...(args);
        static constexpr int_type arr[]{args...};
    
        static bool contains(int_type);
    };
    template<int_type ... args>
    constexpr int_type index_list<args...>::arr[];
    
    template<typename, typename> struct concat;
    template<int_type ... args1, int_type ... args2>
    struct concat<index_list<args1...>, index_list<args2...>> : index_list<args1..., args2...> {};
    
    template<std::size_t N>
    struct make_indices : concat<typename make_indices<N-1>::identity, index_list<N-1>> {}; // Edit²: Ok, zweiter Bug gefixt. Jetzt geht es aber. Hoffentlich.
    template<> struct make_indices<0> : index_list<> {};
    
    template<typename, typename, std::size_t start, std::size_t len> struct sub_list_impl;
    template<int_type ... args, int_type ... indices, std::size_t start, std::size_t len>
    struct sub_list_impl<index_list<args...>, index_list<indices...>, start, len> :
        index_list< index_list<args...>::arr[indices + start]...> {};
    
    template<typename, std::size_t start, std::size_t len> struct sub_list;
    template<int_type ... args, std::size_t start, std::size_t len>
    struct sub_list<index_list<args...>, start, len> : sub_list_impl<index_list<args...>, typename make_indices<len>::identity, start, len>
    {
        static_assert( start + len <= sizeof...(args), "invalid range!" );
    };
    
    template<int_type ... args>
    bool index_list<args...>::contains(int_type val)
    {
        // contains muss noch für Listen mit Größe null und eins "spezialisiert" werden
    
        if( length == 1 )
            return val == arr[0];
    
        return val > arr[length / 2 - 1] ?
                sub_list<identity, length / 2, length / 2 + length % 2>::contains(val)
              : sub_list<identity, 0, length / 2>::contains(val);
    }
    
    #include <iostream>
    int main()
    {
        std::cout << index_list<1, 2, 3, 4, 5>::contains(2);
    }
    


  • Mir fällt auf, mein sub_list -Konstrukt ist schön. Keine Rekursion. Schön direkt.
    Daher kann man verschiedene Dinge damit schön schreiben:

    template<typename, std::size_t> struct cyclic_shift_list;
    template<int_type... args, std::size_t size>
    struct cyclic_shift_list<index_list<args...>, size> :
        concat<typename sub_list<index_list<args...>, size, sizeof...(args) - size>::identity,
               typename sub_list<index_list<args...>, 0, size>::identity> {};
    

    Oder

    template<typename> struct accumulate;
    
    template<int_type ... args>
    struct accumulate<index_list<args...>>
    {
        typedef index_list<args...> list;
        static constexpr int_type value = list::arr[list::length - 1] 
                                        + accumulate<typename sub_list<list, 0, list::length - 1>::identity>::value;
    };
    
    template<int_type first>
    struct accumulate<index_list<first>> :
        std::integral_constant<int_type, first> {};
    

    Das interessante an dieser Definition von accumulate ist die Tatsache, dass accumulate<1, 2, 3, 4> zu 4 + accumulate<1, 2, 3>::value wird - aber accumulate<1, 2, 3> wurde schon,
    im Zuge der Expansions-Reihenfolge von partial_sum(_impl) , vorher instantiiert(!).
    Also keine wirkliche Rekursion, da im Prinzip nur zwei vorhandene Werte addiert werden. Natürlich nur beim speziellen Fall von partial_sum .
    Daher ist das recht effizient. Und das ganze ist dann auch campers Aufgabe entsprechend - es gibt zwar eine Rekursion, aber nur bei make_indices .
    Dieses Prinzip, wo die Rekursion erschnellt wird in dem die Spezialisierungen alle von unten nach oben instantiiert werden, kann man bestimmt auch auf andere Dinge übertragen.

    #include <type_traits>
    
    typedef unsigned int_type;
    
    template<int_type ... args>
    struct index_list
    {
        typedef index_list identity;
        static constexpr auto length = sizeof...(args);
        static constexpr int_type arr[]{args...};
    };
    template<int_type ... args>
    constexpr int_type index_list<args...>::arr[];
    
    /// concat + make_indices: ///////////////////////////////////////////////////////////////////////////////////////
    
    template<typename, typename> struct concat;
    template<int_type ... args1, int_type ... args2>
    struct concat<index_list<args1...>, index_list<args2...>> : index_list<args1..., args2...> {};
    
    template<std::size_t N>
    struct make_indices : concat<typename make_indices<N-1>::identity, index_list<N-1>> {};
    template<> struct make_indices<0> : index_list<> {};
    
    /// sub-list: ///////////////////////////////////////////////////////////////////////////////////////
    
    template<typename, typename, std::size_t start, std::size_t len> struct sub_list_impl;
    template<int_type ... args, int_type ... indices, std::size_t start, std::size_t len>
    struct sub_list_impl<index_list<args...>, index_list<indices...>, start, len> :
        index_list< index_list<args...>::arr[indices + start]...> {};
    
    template<typename, std::size_t start, std::size_t len> struct sub_list;
    template<int_type ... args, std::size_t start, std::size_t len>
    struct sub_list<index_list<args...>, start, len> : sub_list_impl<index_list<args...>, typename make_indices<len>::identity, start, len>
    {
        static_assert( start + len <= sizeof...(args), "invalid range!" );
    };
    
    /// accumulate: ///////////////////////////////////////////////////////////////////////////////////////
    
    template<typename> struct accumulate;
    
    template<int_type ... args>
    struct accumulate<index_list<args...>>
    {
        typedef index_list<args...> list;
        static constexpr int_type value = list::arr[list::length - 1]
                                        + accumulate<typename sub_list<list, 0, list::length - 1>::identity>::value;
    };
    
    template<int_type first>
    struct accumulate<index_list<first>> :
        std::integral_constant<int_type, first> {};
    
    /// partial-sum: ///////////////////////////////////////////////////////////////////////////////////////
    
    template<typename,
             typename>
    struct partial_sum_impl;
    
    template<int_type ... values,
             int_type ... indices>
    struct partial_sum_impl<index_list<values...>, index_list<indices...>> :
        index_list< accumulate< typename sub_list<index_list<values...>, 0, indices + 1>::identity >::value... > {};
    
    template<int_type ... args>
    struct partial_sum :
        partial_sum_impl<index_list<args...>, typename make_indices<sizeof...(args)>::identity> {};
    
    /// ///////////////////////////////////////////////////////////////////////////////////////
    
    #include <iostream>
    #include <iterator>
    int main()
    {
        typedef partial_sum<1, 2, 3, 4> mul;
        std::copy(std::begin(mul::arr), std::end(mul::arr), std::ostream_iterator<int_type>(std::cout, ", "));
    }
    


  • Ihr sucht Aufgaben: Generierung einer Sortierfunktion basierend auf Sortiernetzwerke. Breite, also Anzahl der zu sortierenden Elemente, ist zur Kompilezeit bekannt.


  • Mod

    Sone schrieb:

    Das interessante an dieser Definition von accumulate ist die Tatsache, dass accumulate<1, 2, 3, 4> zu 4 + accumulate<1, 2, 3>::value wird - aber accumulate<1, 2, 3> wurde schon,
    im Zuge der Expansions-Reihenfolge von partial_sum(_impl) , vorher instantiiert(!).
    Also keine wirkliche Rekursion, da im Prinzip nur zwei vorhandene Werte addiert werden. Natürlich nur beim speziellen Fall von partial_sum .

    Das ist wahrscheinlich ein Trugschluss. M.W.s schreibt der Standard die Reihenfolge der Instantiierung nicht vorgegeben für Templates mit dem gleichen Instantiierungspunkt nicht vor. Ein Compiler könnte also immer noch wegen Überschreitung der rekursiven Instantiierungstiefe aussteigen (wenn die Liste lang genug ist).

    Die Lösung erfüllt möglicherweise die Aufgabenstellung, so wie sie gestellt wurde, nicht aber, wie sie gemeint war. Was ich nicht möchte, ist, dass das Argumentpack (auch nicht irgendwie verpackt) stückweise gekürzt und verarbeitet wird (also Zerlegung in Elementzahl+Rest, wobei die Länge des Rests von der ursprünglichen Packgröße abhängt). Deine Lösung geht schon in die richtige Richtung, indem das Problem ein bisschen platter geklopft wurde, aber es fehlt noch etwas.

    @knivil: Kannst du ein kurzes Beispiel machen? Ich sehe die Möglichkeit, die parallelen Sortierschritte mittels Threads/openmp&co. oder über Vektorformen durchzuführen.


  • Mod

    kleine Aufgabe für zwischendurch:
    maps
    Gegeben seien zwei index_listen gleicher Länge und eine zusätzliche Zahl.
    wenn die Zahl in der ersten Liste enthalten ist, soll der entsprechende Wert aus der zweiten Liste zurückgegeben werden.
    Keine Rekursion.
    lookup<map<index_list<2,3,5,7>,index_list<1,2,3,5>>,5>::value ergibt 3
    Bonuspunkte, wenn die map bidirektionales Lookup erlaubt.



  • Was spricht gegen std::binary_search?

    Ich wusste bis eben nicht dass der Compiler bei

    template<int... Values>
    bool contains(int v)
    {
        int arr[sizeof... Values] = { Values... };
        return binary_search(begin(arr), end(arr), v);
    }
    

    wirklich sämtlich Overhead rausoptimiert, zumindestens macht das der GCC bei -O3.

    So macht es natürlich weniger Sinn. Trotzdem schöne Lösungen ihr 2. 👍



  • Ich habe gerade, glaube ich, einen genialen Trick gefunden. 💡 💡 💡



  • Sone schrieb:

    Ich habe gerade, glaube ich, einen genialen Trick gefunden. 💡 💡 💡

    Dann teil ihn wenigstens mit uns.



  • fghfghfgh schrieb:

    Sone schrieb:

    Ich habe gerade, glaube ich, einen genialen Trick gefunden. 💡 💡 💡

    Dann teil ihn wenigstens mit uns.

    Mit GCC 4.8 wird das nix. Versuch es jetzt mit Clang... (Edit: Scheiße. Ich krieg Clang nicht installiert. Führt wohl nix um Linux herum... *sigh*)

    template<int N>
    struct Tag
    {
        static constexpr int value = N;
    
        constexpr Tag(){}
    
        template<int N2>
        constexpr Tag<N+N2> operator,(Tag<N2>)
        { return Tag<N+N2>(); }
    };
    

    ➡

    decltype( Tag<args>... )::value
    


  • camper schrieb:

    kleine Aufgabe für zwischendurch:
    maps
    Gegeben seien zwei index_listen gleicher Länge und eine zusätzliche Zahl.
    wenn die Zahl in der ersten Liste enthalten ist, soll der entsprechende Wert aus der zweiten Liste zurückgegeben werden.
    Keine Rekursion.
    lookup<map<index_list<2,3,5,7>,index_list<1,2,3,5>>,5>::value ergibt 3
    Bonuspunkte, wenn die map bidirektionales Lookup erlaubt.

    template<typename, typename, int> struct lookup;
    template<int ... keys, int ... vals, int index>
    struct lookup<index_list<keys...>, index_list<vals...>, index>
    {
        typedef index_list<((keys == index) * vals)...> hilighted_second;
        typedef index_list<((vals == index) * keys)...> hilighted_first;
    };
    

    Jetzt muss man nur noch aufaddieren (ggf. mit dem Trick oben) und man hat die Werte. Null bei keinem Fund, Summe der Werte bei mehreren Funden... letzteres ist ein Problem, nehme ich an?

    Edit: Ach, wenn dieser Trick oben ginge... man könnte so viel geiles damit machen...


  • Mod

    Sone schrieb:

    fghfghfgh schrieb:

    Sone schrieb:

    Ich habe gerade, glaube ich, einen genialen Trick gefunden. 💡 💡 💡

    Dann teil ihn wenigstens mit uns.

    Mit GCC 4.8 wird das nix. Versuch es jetzt mit Clang... (Edit: Scheiße. Ich krieg Clang nicht installiert. Führt wohl nix um Linux herum... *sigh*)

    template<int N>
    struct Tag
    {
        static constexpr int value = N;
    
        constexpr Tag(){}
    
        template<int N2>
        constexpr Tag<N+N2> operator,(Tag<N2>)
        { return Tag<N+N2>(); }
    };
    

    ➡

    decltype( Tag<args>... )::value
    

    Mit clang wird das auch nichts werden. Das durch eine Packexpansion erzeugte Komma ist niemals ein Kommaoperator.



  • camper schrieb:

    Mit clang wird das auch nichts werden. Das durch eine Packexpansion erzeugte Komma ist niemals ein Kommaoperator.

    Verdammt...!


  • Mod

    Kleiner Tip: Templateargumentlisten sind nicht die einzigen Stellen, wo Packexpansionen auftreten können (14.5.3/4)



  • Spielst du auf sowas an?

    #include <type_traits>
    
    template<typename base, int val>
    struct AccTag
    {
        static int constexpr value = val * std::is_base_of<AccTag<base, val>, base>::value + AccTag<base, val-1>::value;
    };
    
    template<typename base>
    struct AccTag<base, 0> : std::integral_constant<int, 0> {};
    
    template<int ... args>
    struct accumulate : AccTag<accumulate<args...>, args>... 
    // Trick: Da AccTag von einem Template-Parameter abhängt, wird seine Definition erst wenn sie gebraucht wird angesehen - wenn accumulate schon *vollständig definiert* ist.
    {
        static int constexpr value = AccTag<accumulate<args...>, 10>::value; // Hmm. Das muss schöner gehen, ich mach mich ran.
    };
    

    Was an dieser Lösung schön ist: Hier wird nix aufgeteilt, wie du wolltest. 🙂
    Das Blöde: Die Instantiierungstiefe überschreitet ggf. ihr Maximum. Doof. 😞

    P.S.: Empfehle mir mal das beste Linux-Derivat für GCC. Einfach fürs programmieren mit GCC. Gentoo? (Edit: Naja, Gentoo wäre mir vielleicht zuviel. Das ist schon extrem)



  • Irgendeine Distro die Bleeding Edge Pakete anbietet wenn es dir speziell um die neuesten Versionen geht. Fedora ist einsteigerfreundlicch und du kannst meisten relativ neuen Kram haben. Obwohl ich mit Fedora 17 immer noch den 4.7.2 habe, gab wohl keinen Grund umzustellen...



  • Ok, das ist Blödsinn. Da wäre

    template<int ... args>
    struct index_list
    {
        static constexpr int arr[]{args...};
    };
    
    template<typename base, int index>
    struct AccTag
    {
        static int constexpr value = base::arr[index] + AccTag<base, index-1>::value;
    };
    
    template<typename base>
    struct AccTag<base, -1> : std::integral_constant<int, 0> {};
    
    template<int ... args>
    struct accumulate : std::integral_constant<int, AccTag<index_list<args...>, sizeof...(args)-1>::value> {};
    

    Schon direkter.


Anmelden zum Antworten