Search code examples
c++recursiontemplate-meta-programmingllvm-clang

How to understand libcxx's implementation of make_integer_sequence?


Two related commits I found:

  1. https://github.com/llvm-mirror/libcxx/commit/42e55e932e173eb224997fe11f0d15a1d74b29dc
  2. https://github.com/llvm-mirror/libcxx/commit/a3ccd96ede26a2f383328234e01eb7a9f870691e

The previous __make_tuple_indices implementation caused O(N) instantiations and was pretty inefficient. The C++14 __make_integer_sequence implementation is much better, since it either uses a builtin to generate the sequence or a very nice Log8(N) implementation provided by richard smith.

This patch moves the __make_integer_sequence implementation into __tuple and uses it to implement __make_tuple_indices.

Since libc++ can't expose the name 'integer_sequence' in C++11 this patch also introduces a dummy type '__integer_sequence' which is used when generating the sequence. One the sequence is generated '__integer_sequence' can be converted into the required type; either '__tuple_indices' or 'integer_sequence'.


From the commit, I know it s a Log8(N) implementation, which manually unroll the loops(If it is not right , please correct me, thx). But I cannot understand how namespace detail work with __integer_sequence. I have tried to use debugger, but it always uses the __has_builtin(__make_integer_seq) branch.


So, please help me understand this implementation, main codes are in this commit and this part of <utility>:

// <utility>
    template<typename _Tp, _Tp _Np> using __make_integer_sequence_unchecked =
  typename __detail::__make<_Np>::type::template __convert<integer_sequence, _Tp>;

template <class _Tp, _Tp _Ep>
struct __make_integer_sequence_checked
{
    static_assert(is_integral<_Tp>::value,
                  "std::make_integer_sequence can only be instantiated with an integral type" );
    static_assert(0 <= _Ep, "std::make_integer_sequence must have a non-negative sequence length");
    // Workaround GCC bug by preventing bad installations when 0 <= _Ep
    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68929
    typedef __make_integer_sequence_unchecked<_Tp, 0 <= _Ep ? _Ep : 0> type;
};

template <class _Tp, _Tp _Ep>
using __make_integer_sequence = typename __make_integer_sequence_checked<_Tp, _Ep>::type;

// <__tuple>

template <class _IdxType, _IdxType... _Values>
struct __integer_sequence {
  template <template <class _OIdxType, _OIdxType...> class _ToIndexSeq, class _ToIndexType>
  using __convert = _ToIndexSeq<_ToIndexType, _Values...>;

  template <size_t _Sp>
  using __to_tuple_indices = __tuple_indices<(_Values + _Sp)...>;
};

template<typename _Tp, size_t ..._Extra> struct __repeat;
template<typename _Tp, _Tp ..._Np, size_t ..._Extra> struct __repeat<__integer_sequence<_Tp, _Np...>, _Extra...> {
  typedef __integer_sequence<_Tp,
                           _Np...,
                           sizeof...(_Np) + _Np...,
                           2 * sizeof...(_Np) + _Np...,
                           3 * sizeof...(_Np) + _Np...,
                           4 * sizeof...(_Np) + _Np...,
                           5 * sizeof...(_Np) + _Np...,
                           6 * sizeof...(_Np) + _Np...,
                           7 * sizeof...(_Np) + _Np...,
                           _Extra...> type;
};

template<size_t _Np> struct __parity;
template<size_t _Np> struct __make : __parity<_Np % 8>::template __pmake<_Np> {};

template<> struct __make<0> { typedef __integer_sequence<size_t> type; };
template<> struct __make<1> { typedef __integer_sequence<size_t, 0> type; };
template<> struct __make<2> { typedef __integer_sequence<size_t, 0, 1> type; };
template<> struct __make<3> { typedef __integer_sequence<size_t, 0, 1, 2> type; };
template<> struct __make<4> { typedef __integer_sequence<size_t, 0, 1, 2, 3> type; };
template<> struct __make<5> { typedef __integer_sequence<size_t, 0, 1, 2, 3, 4> type; };
template<> struct __make<6> { typedef __integer_sequence<size_t, 0, 1, 2, 3, 4, 5> type; };
template<> struct __make<7> { typedef __integer_sequence<size_t, 0, 1, 2, 3, 4, 5, 6> type; };

template<> struct __parity<0> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type> {}; };
template<> struct __parity<1> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 1> {}; };
template<> struct __parity<2> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 2, _Np - 1> {}; };
template<> struct __parity<3> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<4> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<5> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<6> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<7> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };

} // namespace detail

Thanks in advance.

If you think this question is too border/ruder, feel free to tell me. I'll delete soon, though this question does bother me a lot.


Solution

  • You also need to understand __repeat to see how this works:

    template<typename _Tp, size_t ..._Extra> struct __repeat;
    template<typename _Tp, _Tp ..._Np, size_t ..._Extra> struct __repeat<integer_sequence<_Tp, _Np...>, _Extra...> {
      typedef integer_sequence<_Tp,
                               _Np...,
                               sizeof...(_Np) + _Np...,
                               2 * sizeof...(_Np) + _Np...,
                               3 * sizeof...(_Np) + _Np...,
                               4 * sizeof...(_Np) + _Np...,
                               5 * sizeof...(_Np) + _Np...,
                               6 * sizeof...(_Np) + _Np...,
                               7 * sizeof...(_Np) + _Np...,
                               _Extra...> type;
    }
    

    It takes two template parementers: An integer sequence and a paremeter pack of _Extra values.

    It has a member typedef type that is an integer sequence of the same type as the initial integer sequence.

    It's members are as follows:

    _Np...,  // The original values
    
    
    sizeof...(_Np) + _Np...,
    // sizeof...(_Np) is the number of integers in the sequence. This is a fold expression
    // that adds the sizeof...(_Np) to every integer.
    
    // So (_Np..., sizeof...(_Np) + _Np...) for <0, 1, 2> would be
    // (<0, 1, 2>..., <3 + 0, 3 + 1, 3 + 2>...), which is `<0, 1, 2, 3, 4, 5>`.
    
    // The rest of the lines are the same, but starting with a different
    // multiple of sizeof...(_Np)
    
    // `<0, 1, ..., N>` into an integer sequence of `<0, 1, ..., 8N>`.
    
    _Extra...
    // And then add `_Extra` to the end
    

    __make<_Np> from _Np = 0 to _Np = 7 is hard coded. Otherwise, it uses __parity as a helper type.

    This will use __repeat to repeat __make<_Np / 8> 8 times, creating the desired length, and then add the remaining items using extra based on how much bigger it is than the last multiple of 8 (Called "parity" here) as _Extra.

    It's not so much "manually unrolling the loop". It's just recursively dividing make_integer_sequence<N> into repeat_8_times<make_integer_sequence<N / 8>> /* + remainder */, so it's "recursion with a base case"