跟我学c++中级篇—make_index_sequence

一、从一个例子开始

#include <iostream>
#include <array>
#include <type_traits>
//这个例程必须在c++17及以上
template <size_t ...N>
static constexpr auto Square(size_t index, std::index_sequence<N ...>) 
{
    //如果在c++17之下,下面会报常量初始化错误,注意下面这个省略号的应用
    constexpr auto nums = std::array{ N * N ... };
    return nums[index];
}

template <size_t N>
constexpr static auto BuildArray(size_t index) 
{
    //生成初始化数组的向量,调用初始化的目的是为了展开Square模板中的N,也就那个...
    return Square(index, std::make_index_sequence<N>{});
}

int main() 
{
    static_assert(BuildArray<16>(3) == 3 * 3);
    auto b = BuildArray<31>(20) == 20 * 20;
    std::cout << "bool:" << b << std::endl;
}

这是一个很简单的例子,用make_index_sequence展开来得到数组,并验证得到的数组是不是N的平方,验证的方式其实就是通过std::index_sequence得到具体的索引来进行。例子简单,但里面这个STL中的make_index_sequence相关的应用是如何得来的,却是让新人头脑有点转不过来。下面就对期进行分析,而分析用到的这些知识,在前面都已经讲过了。

二、make_index_sequence的分析说明

在上面的引入例子里,std::make_index_sequence{},使用大括号进行初始化,这个在以前的分析中也说明过,它的作用主要就是得到模板中的具体的实例,也就是{0,1,2,…N},为了更清楚的看明白,可以把编译器的版本回退到c++14,则报的错误中有下面的场景:

“查看对正在编译的函数 模板 实例化“auto Square<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>(size_t,std::integer_sequence<size_t,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>)”的引用”。
是不是就明白了,所以有一个好用的编译器工具非常省时间省心思。而后面的std::array{ N * N … }不过是对展开参数的一种应用。下面看一下make_index_sequence的定义:

template< class T, T... Ints >
struct integer_sequence;
模板形参
T	-	用于序列元素的整数类型
...Ints	-	表示序列的非类型形参包

而为了辅助实现这个类,又定义了下面的几个辅助类:

为帮助 T 为 std::size_t 的常用情况,定义辅助别名模板 std::index_sequence 。

template<std::size_t... Ints>
using index_sequence = std::integer_sequence<std::size_t, Ints...>;
分别定义辅助别名模板 std::make_integer_sequence 与 std::make_index_sequence 以简化以 0, 1, 2, ..., N-1 为 Ints 创建 std::integer_sequence 与 std::index_sequence 类型:

template<class T, T N>
using make_integer_sequence = std::integer_sequence<T, /* a sequence 0, 1, 2, ..., N-1 */ >;
template<std::size_t N>
using make_index_sequence = std::make_integer_sequence<std::size_t, N>;
若 N 为负则程序为谬构。若 N 为零,则指示类型为 integer_sequence<T> 。

定义辅助类模板 std::index_sequence_for ,以转换任何类型参数包为同长度的下标序列:

template<class... T>
using index_sequence_for = std::make_index_sequence<sizeof...(T)>;

通过上述的定义明白了,make_index_sequence只是integer_sequence的一种特化。其实就是一种因为特化不同导致的名字变来变去,一定要看清楚。弄明白了这个,就可以想到std::tuple的访问了,一般来说访问一个Tuple,会用Get依次获取值即可。但如果用make_index_sequence则会有不错的效果来处理这些情况,看下面例程:


template <typename Tuple, typename Func, size_t ... N>
void TupleCall(const Tuple& t, Func&& func, std::index_sequence<N...>) {
    static_cast<void>(std::initializer_list<int>{(func(std::get<N>(t)), 0)...});
}

template <typename ... Args, typename Func>
void TupleTravel(const std::tuple<Args...>& t, Func&& func) {
    TupleCall(t, std::forward<Func>(func), std::make_index_sequence<sizeof...(Args)>{});
}

void test() 
{
    auto t = std::make_tuple(1, 3.68, "how are you");
    TupleTravel(t, [](auto&& au) {
        std::cout << au << ","<<std::endl;
        });
}
int main() 
{
    test();
    return 0;
}

有了开始的代码引入,再理解这个就好理解了,其实就是通过make_index_sequence展开通过sizeof …(Args)得到变参数量,std::index_sequence得其索引,利用std::get得到具体的数据,注意这个std::initializer_list的用法,在以前讲变参模板时,就有这种方法,用0的目的是这个数据初始化没有啥用处。
其实在c++17中,也是利用这个原理来实现了std::apply,std::tuple就好搞多了,在前面的《c++17中的apply和make_from_tuple》也有说明。

三、编程实现

既然有珠玉在前,咱们就试着也按思路实现一下:

#include <iostream>

//自定义整型序列,不要和STL中的index_sequence 混淆
template<int...Ids>
struct IdSeq {};

//继承并展开参数包
template<int N, int... Ids>
struct MakeIds : MakeIds<N - 1, N - 1, Ids...> {};

// 通过模板特化,终止展开包,想想c++17中的折叠表达式
template<int... Ids>
struct MakeIds<0, Ids...>
{
    //typedef IdSeq<Ids...> type;
    //using type = IdSeq<Ids ...>;
    typename typedef IdSeq<Ids ...> type;
};

int main()
{
    using T = MakeIds<9>::type;
    std::cout << typeid(T).name() << std::endl;

    return 0;
}

看了上面的例程,再看下面的代码就好理解了:

template<int... N>
struct index_seq {};

template<int N, int ...M>
struct make_index_seq : public make_index_seq<N - 1, N - 1, M...> {

};

template<int ...M>
struct make_index_seq<0, M...> : public index_seq<M...> {};

网上还有一个尾插的实现:

template<size_t ... Args>
struct index_sequence {};

template<typename T, size_t newValue>
struct AppendNewValue;

template<size_t... Args, size_t newValue>
struct AppendNewValue<index_sequence<Args...>, newValue> {
    using type = index_sequence<Args..., newValue>;
};

template<size_t N>
struct make_index_sequence
{
    using type = typename AppendNewValue<typename make_index_sequence<N - 1>::type, N - 1>::type;
};

template<>
struct make_index_sequence<0>
{
    using type = index_sequence<>;
};

int main()
{
    using T2 = make_index_sequence<9>::type;
    std::cout << typeid(T2).name() << std::endl;

    return 0;
}

注意,上面的make_index_sequence和index_sequence 都没有std::,这是和STL中的相关的区别开来,为了安全,可以增加自己的名空间。
无论上头插实现还是尾插实现,都配合着看一下代码,就没有了秘密可言。
在StackOverFlow上https://stackoverflow.com/questions/49669958/details-of-stdmake-index-sequence-and-stdindex-sequence上有这个讨论的实现和说明下面只把代码贴上来,有兴趣可以和上面的对比一下:

#include <cstddef>

namespace A
{
    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>())>;
};

///
namespace B
{
    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>());
};

有一个国内转载的汉化地址:https://www.likecs.com/ask-882834.html#sc=400,同时网上有一个源码分析,类似于这个尾插法实现的,有兴趣可以去搜一下。

四、总结

本来一直觉得这个std::make_index_sequence没有什么可讲的,可在往后分析元编程时发现,这个还挺有代表性,所以就拿出来时间把它专门整理了一下,其实越分析,越往后学习,就会发现,其实这些基础的东西组合起来,就可以发挥出一些令人拍案的用法。
这和区块链一样,区块链本身并没有什么创新的技术,只是多种技术的工程化组合,就创造出了一个新兴的领域。以此为借鉴吧,努力!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值