我很喜欢使用可变参数模板,并开始摆弄这个新功能。我试图了解std::index_sequence
的实现细节(用于元组实现)。我看到那里的示例代码,但我真的想要一步一步地解释std::index_sequence
如何被编码以及每个阶段的元编程主题。认为真的愚蠢了:)
答案 0 :(得分:12)
我看到那里的示例代码,但我真的想要逐步解释如何编码index_sequence以及每个阶段的元编程主题。
你问的问题并不是很容易解释......
嗯...... std::index_sequence
本身非常简单:定义如下
template<std::size_t... Ints>
using index_sequence = std::integer_sequence<std::size_t, Ints...>;
实际上,它是无符号整数的模板容器。
棘手的部分是std::make_index_sequence
的实施。也就是说:棘手的部分是从std::make_index_sequence<N>
传递到std::index_sequence<0, 1, 2, ..., N-1>
。
它建议你一个可能的实现(不是一个很好的实现,但很简单(我希望)理解),我将尝试解释它是如何工作的。
完全不是从std::integer_sequence
传递的标准索引序列,但修复了std::size_t
类型,您可以使用以下代码获得合理的indexSequence
/ makeIndexSequence
对。
// index sequence only
template <std::size_t ...>
struct indexSequence
{ };
template <std::size_t N, std::size_t ... Next>
struct indexSequenceHelper : public indexSequenceHelper<N-1U, N-1U, Next...>
{ };
template <std::size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
{ using type = indexSequence<Next ... >; };
template <std::size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;
我认为理解它是如何工作的好方法是遵循一个实际的例子。
我们可以看到makeIndexSequence<3>
成为index_sequenxe<0, 1, 2>
的方式。
我们认为makeIndexSequence<3>
定义为typename indexSequenceHelper<3>::type
[N
为3
]
indexSequenceHelper<3>
仅匹配一般情况,因此继承自indexSequenceHelper<2, 2>
[N
为3
且Next...
为空]
indexSequenceHelper<2, 2>
仅匹配一般情况,因此继承自indexSequenceHelper<1, 1, 2>
[N
为2
而Next...
为2
] < / p>
indexSequenceHelper<1, 1, 2>
仅匹配一般情况,因此继承自indexSequenceHelper<0, 0, 1, 2>
[N
为1
而Next...
为1, 2
] < / p>
indexSequenceHelper<0, 0, 1, 2>
匹配两种情况(一般是部分特化),因此应用了部分特化,定义type = indexSequence<0, 1, 2>
[Next...
为0, 1, 2
]
结论:makeIndexSequence<3>
是indexSequence<0, 1, 2>
。
希望这有帮助。
---编辑---
一些澄清:
std::index_sequence
和std::make_index_sequence
可从C ++ 14开始
我的例子很简单(我希望)能够理解但是(正如aschepler所指出的那样)有一个很大的限制,即线性实现;我的意思是:如果您需要index_sequence<0, 1, ... 999>
,请使用makeIndexSequence<1000>
以递归方式实现1000个indexSequenceHelper
;但是有一个递归限制(编译器形式编译器不同),可以小于1000;还有其他算法可以限制递归次数,但解释起来比较复杂。
答案 1 :(得分:5)
为了完整起见,我将使用std::make_index_sequence
和if constexpr
添加更为现代的auto
实现,使模板编程更像&#34 ;正常&#34;节目。
template <std::size_t... Ns>
struct index_sequence {};
template <std::size_t N, std::size_t... Is>
auto make_index_sequence_impl() {
// only one branch is considered. The other may be ill-formed
if constexpr (N == 0) return index_sequence<Is...>(); // end case
else return make_index_sequence_impl<N-1, N-1, Is...>(); // recursion
}
template <std::size_t N>
using make_index_sequence = std::decay_t<decltype(make_index_sequence_impl<N>())>;
我强烈建议使用这种模板编程风格,这更容易推理。
答案 2 :(得分:0)
以免被遗忘:
template <std::size_t N, std::size_t ...I>
constexpr auto make_index_sequence_impl() noexcept
{
if constexpr (!N)
{
return std::index_sequence<I...>();
}
else if constexpr (!sizeof...(I))
{
return make_index_sequence_impl<N - 1, 0>();
}
else if constexpr (N >= sizeof...(I))
{
return make_index_sequence_impl<N - sizeof...(I), I..., sizeof...(I) + I...>();
}
else
{
return []<auto ...J>(std::index_sequence<J...>) noexcept
{
return std::index_sequence<I..., sizeof...(I) + J...>();
}(make_index_sequence_impl<N>()); // index concatenation
}
}
template <size_t N>
using make_index_sequence = decltype(make_index_sequence_impl<N>());