Реализация С++ 14 make_integer_sequence

Я попытался реализовать шаблон псевдонима С++ 14 make_integer_sequence, который упрощает создание шаблона класса 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.

Для реализации make_integer_sequence нам нужна вспомогательная структура make_helper.

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

Реализация make_helper не слишком сложна.

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;
};

Чтобы проверить make_integer_sequence, я сделал эту основную функцию:

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

   BOOST_PP_REPEAT(256, GEN, ~);
}

Я скомпилировал программу с GCC 4.8.0 на четырехъядерной системе i5 с 8 ГБ оперативной памяти. Успешная компиляция заняла 4 секунды.

Но когда я сменил макрос GEN на:

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, ~ );
}

Компиляция не удалась и вышла сообщение об ошибке:

исчерпана виртуальная память.

Может кто-нибудь объяснить эту ошибку и что вызвало ее?

EDIT:

Я упростил тест, чтобы:

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

Затем я успешно скомпилировал с GCC 4.8.0 -ftemplate-depth = 65536.

Однако это второе испытание:

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

Не компилировался с GCC 4.8.0 -ftemplate-depth = 65536 и приводил к ошибке:

виртуальная память исчерпана.

Итак, мой вопрос заключается в том, как уменьшить шаблонную копию шаблона?

С уважением, Хуршид.

Ответ 1

Здесь реализована реализация log N, которая даже не требует увеличения максимальной глубины для экземпляров шаблонов и компиляции довольно быстро:

// 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>{};

Ответ 2

Это в основном я, хакинг вокруг решения Xeo: Создание сообщества wiki - если благодарность, , пожалуйста, повысьте Xeo.

... только что изменился, пока не почувствовал, что он не может быть проще, переименован и добавлен value_type и size() для стандарта (но только делает index_sequence not integer_sequence), а код работает с GCC 5.2 -std=c++14 может работать в противном случае без изменений в старых/других компиляторах, за которыми я застрял. Мог бы сэкономить кому-нибудь время/путаницу.

// 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> { };
}

Примечания:

  • "магия" решения Xeo заключается в объявлении _merge_and_renumber (concat в его коде) с ровно двумя параметрами, в то время как specilisation эффективно предоставляет свои индивидуальные пакеты параметров

  • typename... ::type в...

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

      избегает ошибки:

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

Ответ 3

Я нашел очень быструю и ненужную реверсивную версию реализации make_index_sequence. На моем ПК он компилируется с N = 1 048 576, с 2 с. (ПК: Centos 6.4 x86, i5, 8 Gb RAM, gcc-4.4.7 -std = С++ 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(){}

Ответ 4

Здесь отсутствует -1:

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

в частности:

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

Затем первая ветвь не должна быть integer_sequence<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;

который должен быть достаточным для компиляции исходного кода.

В общем, при написании серьезного метапрограммирования template ваша основная цель должна заключаться в том, чтобы сохранить глубину template. Способ подумать об этой проблеме - представить, что у вас есть компьютер с бесконечным потоком: каждый независимый расчет следует перетасовать на свой собственный поток, а затем перетасовать вместе в конце. У вас есть несколько операций, которые принимают O (1) глубину, например расширение ...: используйте их.

Обычно вытягивание логарифмической глубины достаточно, потому что с глубиной 900, которая позволяет структурам размером 2^900, и что-то еще ломается первым. (Чтобы быть справедливым, более вероятно, что это 90 различных уровней структур размером 2^10).

Ответ 5

Вот еще несколько более общий вариант Xeo логарифмической ответ, который обеспечивает make_integer_sequence для любых типов. Это делается с помощью std::integral_constant, чтобы избежать ошибки "аргумента шаблона, связанного с параметром шаблона".

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>;

Демо- версия: coliru

Ответ 6

Простая реализация O (N). Вероятно, это не то, что вы хотите для большого N, но мое приложение предназначено только для вызовов функций с индексированными аргументами, и я не ожидал бы, что arity больше, чем около 10. Я не заполнил члены integer_sequence. Я с нетерпением жду использования стандартной реализации библиотеки и nuking это:)

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;