web-dev-qa-db-fra.com

Implémentation C ++ 14 make_integer_sequence

J'ai essayé d'implémenter le modèle d'alias C++ 14make_integer_sequence, ce qui simplifie la création du modèle de classe integer_sequence .

template< class T, T... I> struct integer_sequence
{
    typedef T value_type;
    static constexpr size_t size() noexcept { return sizeof...(I) ; }

};

template< class T, T N>
using make_integer_sequence = integer_sequence< T, 0,1,2, ... ,N-1 >; // only for illustration.

Implémenter make_integer_sequence nous avons besoin d'une structure d'aide make_helper.

template< class T , class N >
using make_integer_sequence = typename make_helper<T,N>::type;

Implémentation de make_helper n'est pas trop difficile.

template< class T, T N, T... I >
struct make_helper
{
   typedef typename mpl::if_< T(0) == N,  
                  mpl::identity< integer_sequence<T,I...> >,
                  make_helper< T, N-1, N-1,I...> 
               >::type;
};

Tester make_integer_sequence J'ai fait cette fonction principale:

int main()
{
    #define GEN(z,n,temp)   \
     typedef make_integer_sequence< int, n >  BOOST_PP_CAT(int_seq,n) ;

   BOOST_PP_REPEAT(256, GEN, ~);
}

J'ai compilé le programme avec GCC 4.8.0, sur un système quadricœur i5 avec 8 Go de RAM. La compilation réussie a pris 4 secondes.

Mais, quand j'ai changé la macro GEN en:

int main() {

#define GEN(z,n,temp) \
typedef make_integer_sequence< int, n * 4 > BOOST_PP_CAT(int_seq, n) ;

BOOST_PP_REPEAT(256, GEN, ~ );
}

La compilation a échoué et a généré le message d'erreur:

mémoire virtuelle épuisée.

Quelqu'un pourrait-il expliquer cette erreur et quelle en est la cause?

ÉDITER:

J'ai simplifié le test pour:

int main()
{
   typedef make_integer_sequence< int, 4096 > int_seq4096;
}

J'ai ensuite compilé avec succès avec GCC 4.8.0 -ftemplate-depth = 65536.

Cependant ce deuxième test:

int main()
{
    typedef make_integer_sequence< int, 16384 > int_seq16384;
}

N'a pas compilé avec GCC 4.8.0 -ftemplate-depth = 65536 et a entraîné l'erreur:

mémoire virtuelle épuisée.

Donc, ma question est, comment puis-je réduire l'instanciation profonde du modèle?

Cordialement, Khurshid.

47
Khurshid

Voici un log N implémentation qui n'a même pas besoin d'une profondeur maximale accrue pour les instanciations de modèles et se compile assez rapidement:

// using aliases for cleaner syntax
template<class T> using Invoke = typename T::type;

template<unsigned...> struct seq{ using type = seq; };

template<class S1, class S2> struct concat;

template<unsigned... I1, unsigned... I2>
struct concat<seq<I1...>, seq<I2...>>
  : seq<I1..., (sizeof...(I1)+I2)...>{};

template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;

template<unsigned N> struct gen_seq;
template<unsigned N> using GenSeq = Invoke<gen_seq<N>>;

template<unsigned N>
struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};

template<> struct gen_seq<0> : seq<>{};
template<> struct gen_seq<1> : seq<0>{};
99
Xeo

C'est essentiellement moi qui pirate la solution de Xeo: Rendre le wiki communautaire - si appréciable, s'il vous plaît, voter Xeo .

... juste modifié jusqu'à ce que je pense que cela ne pourrait pas être plus simple, renommé et ajouté value_type et size() selon la norme (mais seulement en faisant index_sequence pas integer_sequence), Et le code fonctionnant avec GCC 5.2 -std=c++14 Pourrait s'exécuter autrement sans être altéré sous les anciens/autres compilateurs avec lesquels je suis coincé. Cela pourrait faire gagner du temps/de la confusion à quelqu'un.

// based on http://stackoverflow.com/a/17426611/410767 by Xeo
namespace std  // WARNING: at own risk, otherwise use own namespace
{
    template <size_t... Ints>
    struct index_sequence
    {
        using type = index_sequence;
        using value_type = size_t;
        static constexpr std::size_t size() noexcept { return sizeof...(Ints); }
    };

    // --------------------------------------------------------------

    template <class Sequence1, class Sequence2>
    struct _merge_and_renumber;

    template <size_t... I1, size_t... I2>
    struct _merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
      : index_sequence<I1..., (sizeof...(I1)+I2)...>
    { };

    // --------------------------------------------------------------

    template <size_t N>
    struct make_index_sequence
      : _merge_and_renumber<typename make_index_sequence<N/2>::type,
                            typename make_index_sequence<N - N/2>::type>
    { };

    template<> struct make_index_sequence<0> : index_sequence<> { };
    template<> struct make_index_sequence<1> : index_sequence<0> { };
}

Remarques:

  • la "magie" de la solution de Xeo réside dans la déclaration de _merge_and_renumber (concat dans son code) avec exactement deux paramètres, tandis que la spécilisation expose efficacement leurs packs de paramètres individuels

  • le typename... ::type dans ...

    struct make_index_sequence
      : _merge_and_renumber<typename make_index_sequence<N/2>::type,
                            typename make_index_sequence<N - N/2>::type>
    

évite l'erreur:

invalid use of incomplete type 'struct std::_merge_and_renumber<std::make_index_sequence<1ul>, std::index_sequence<0ul> >'
24
Tony Delroy

J'ai trouvé une version de récursion profonde très rapide et inutile de l'implémentation de make_index_sequence. Dans mon PC, il compile avec N = 1 048 576, avec 2 s. (PC: Centos 6.4 x86, i5, 8 Go de RAM, gcc-4.4.7 -std = c ++ 0x -O2 -Wall).

#include <cstddef> // for std::size_t

template< std::size_t ... i >
struct index_sequence
{
    typedef std::size_t value_type;

    typedef index_sequence<i...> type;

    // gcc-4.4.7 doesn't support `constexpr` and `noexcept`.
    static /*constexpr*/ std::size_t size() /*noexcept*/
    { 
        return sizeof ... (i); 
    }
};


// this structure doubles index_sequence elements.
// s- is number of template arguments in IS.
template< std::size_t s, typename IS >
struct doubled_index_sequence;

template< std::size_t s, std::size_t ... i >
struct doubled_index_sequence< s, index_sequence<i... > >
{
    typedef index_sequence<i..., (s + i)... > type;
};

// this structure incremented by one index_sequence, iff NEED-is true, 
// otherwise returns IS
template< bool NEED, typename IS >
struct inc_index_sequence;

template< typename IS >
struct inc_index_sequence<false,IS>{ typedef IS type; };

template< std::size_t ... i >
struct inc_index_sequence< true, index_sequence<i...> >
{
    typedef index_sequence<i..., sizeof...(i)> type;
};



// helper structure for make_index_sequence.
template< std::size_t N >
struct make_index_sequence_impl : 
           inc_index_sequence< (N % 2 != 0), 
                typename doubled_index_sequence< N / 2,
                           typename make_index_sequence_impl< N / 2> ::type
               >::type
       >
{};

 // helper structure needs specialization only with 0 element.
template<>struct make_index_sequence_impl<0>{ typedef index_sequence<> type; };



// OUR make_index_sequence,  gcc-4.4.7 doesn't support `using`, 
// so we use struct instead of it.
template< std::size_t N >
struct make_index_sequence : make_index_sequence_impl<N>::type {};

//index_sequence_for  any variadic templates
template< typename ... T >
struct index_sequence_for : make_index_sequence< sizeof...(T) >{};


// test
typedef make_index_sequence< 1024 * 1024 >::type a_big_index_sequence;
int main(){}
10
Khurshid

Il vous manque un -1 ici:

typedef typename mpl::if_< T(0) == N,  
              mpl::identity< integer_sequence<T> >,
              make_helper< T, N, N-1,I...> 
           >::type;

en particulier:

typedef typename mpl::if_< T(0) == N,  
              mpl::identity< integer_sequence<T> >,
              make_helper< T, N-1, N-1,I...> 
           >::type;

Ensuite, la première branche ne doit pas être integer_sequence<T>, mais plutôt integer_sequence<T, I...>.

typedef typename mpl::if_< T(0) == N,  
              mpl::identity< integer_sequence<T, I...> >,
              make_helper< T, N-1, N-1,I...> 
           >::type;

ce qui devrait être suffisant pour que votre code d'origine soit compilé.

En général, lors de l'écriture d'une métaprogrammation sérieuse template, votre objectif principal doit être de réduire la profondeur de l'instanciation de template. Une façon de penser à ce problème consiste à imaginer que vous avez un ordinateur à thread infini: chaque calcul indépendant doit être mélangé sur son propre thread, puis mélangé à la fin. Vous avez quelques opérations qui prennent O(1) profondeur, comme ... expansion: exploitez-les.

Habituellement, tirer de la profondeur logarithmique est suffisant, car avec un 900 profondeur, qui permet 2^900 structures de taille, et quelque chose d'autre se brise en premier. (Pour être honnête, il est plus probable que 90 couches différentes de 2^10 structures de taille).

4

Voici une autre variation légèrement plus générale de la réponse logarithmique de Xeo qui fournit make_integer_sequence pour les types arbitraires. Pour ce faire, utilisez std::integral_constant afin d'éviter l'argument redouté "l'argument de modèle implique un paramètre de modèle".

template<typename Int, Int... Ints>
struct integer_sequence
{
    using value_type = Int;
    static constexpr std::size_t size() noexcept
    {
        return sizeof...(Ints);
    }
};

template<std::size_t... Indices>
using index_sequence = integer_sequence<std::size_t, Indices...>;

namespace
{
    // Merge two integer sequences, adding an offset to the right-hand side.
    template<typename Offset, typename Lhs, typename Rhs>
    struct merge;

    template<typename Int, Int Offset, Int... Lhs, Int... Rhs>
    struct merge<
        std::integral_constant<Int, Offset>,
        integer_sequence<Int, Lhs...>,
        integer_sequence<Int, Rhs...>
    >
    {
        using type = integer_sequence<Int, Lhs..., (Offset + Rhs)...>;
    };

    template<typename Int, typename N>
    struct log_make_sequence
    {
        using L = std::integral_constant<Int, N::value / 2>;
        using R = std::integral_constant<Int, N::value - L::value>;
        using type = typename merge<
            L,
            typename log_make_sequence<Int, L>::type,
            typename log_make_sequence<Int, R>::type
        >::type;
    };

    // An empty sequence.
    template<typename Int>
    struct log_make_sequence<Int, std::integral_constant<Int, 0>>
    {
        using type = integer_sequence<Int>;
    };

    // A single-element sequence.
    template<typename Int>
    struct log_make_sequence<Int, std::integral_constant<Int, 1>>
    {
        using type = integer_sequence<Int, 0>;
    };
}

template<typename Int, Int N>
using make_integer_sequence =
    typename log_make_sequence<
        Int, std::integral_constant<Int, N>
    >::type;

template<std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;

Démo: colir

3
joki

Implémentation simple O (N). Probablement pas ce que vous voulez pour un grand N, mais mon application est uniquement pour appeler des fonctions avec des arguments indexés, et je ne m'attendrais pas à une arité supérieure à environ 10. Je n'ai pas rempli les membres de integer_sequence. J'ai hâte d'utiliser une implémentation de bibliothèque standard et de nuquer à cela :)

template <typename T, T... ints>
struct integer_sequence
{ };

template <typename T, T N, typename = void>
struct make_integer_sequence_impl
{
    template <typename>
    struct tmp;

    template <T... Prev>
    struct tmp<integer_sequence<T, Prev...>>
    {
        using type = integer_sequence<T, Prev..., N-1>;
    };

    using type = typename tmp<typename make_integer_sequence_impl<T, N-1>::type>::type;
};

template <typename T, T N>
struct make_integer_sequence_impl<T, N, typename std::enable_if<N==0>::type>
{ using type = integer_sequence<T>; };

template <typename T, T N>
using make_integer_sequence = typename make_integer_sequence_impl<T, N>::type;
0
cdyson37