這篇雖然是在講 C++14 的「std::integer_sequence」(cppreference),不過實際上應該算是之前《C++11 的「…」:Parameter Pack》一文的延伸。
std::integer_sequence 是一個用來建立編譯階段(compile-time )的整數序列(不是陣列)的 template class,定義如下:
template< class T, T... Ints > class integer_sequence;
而他是被定義在標準函式庫的 <utility> 這個標頭檔裡面,所以要使用的話,就需要引入這個檔案。
他最基本的使用方法大致上如下:
std::integer_sequence<int, 1, 3, 5, 7>()
這樣就代表了一個型別是 int 的序列、裡面有 1, 3, 5, 7 四個數值。
不過如果要使用這個序列,並不能直接用,而是要使用 Parameter Pack 的 Pack Expansion 的方法,才能存取裡面的值。
比如說想要把上面的 std::integer_sequence 轉換成 std::array 的形式的話,那可以寫成下面的樣子:
#include <utility> #include <array> template<class T, T... I> std::array<T, sizeof...(I)> make_array(std::integer_sequence<T, I...>) { return{ I... }; } int main() { std::array<int,4> x
= make_array( std::integer_sequence<int, 1, 3, 5, 7>() ); return 0; }
透過 make_array() 這個函式,就可以把 std::integer_sequence 的數列轉換成 std::array<int,4> 了。
而如果是要產生連續遞增的序列的話,則可以直接使用 std::make_integer_sequence() 這個函式,來快速地產生 0, 1,…, N-1 這樣的序列。
例如下面這樣的程式:
std::array<int,7> x = make_array(std::make_integer_sequence<int, 7>());
執行後,x 就會是 { 0, 1, 2, 3, 4, 5, 6 } 這樣的陣列。
而如果要更簡化的話,他也還有提供一個特化成 size_t 的版本、index_sequence,其定義如下:
template<std::size_t... Ints> using index_sequence = std::integer_sequence<std::size_t, Ints...>;
而他也有對應的 make_index_sequence() 可以用。下面就是簡單的用法:
std::array<size_t,7> x = make_array( std::make_index_sequence<7>() );
和前面使用 make_integer_sequence() 的例子相比,唯一的差別就是 x 裡面每一項的型別都是 size_t 了。
而這東西到底有什麼用呢?
一個例子,或許可以考慮是容器類型的轉換,例如下面就是把 std::array<> 的資料,轉換成 std::tuple<>:
#include <utility> #include <array> #include <tuple> template<typename Array, std::size_t... I> auto a2t_impl(const Array& a, std::index_sequence<I...>) { return std::make_tuple(a[I]...); } template<typename T, std::size_t N, typename Indices = std::make_index_sequence<N>> auto a2t(const std::array<T, N>& a) { return a2t_impl(a, Indices()); } int main() { std::array<int, 5> aData = { 1,2,3,4,5 }; auto aDatatTuple = a2t(aData); return 0; }
透過上面的函式,就可以把 std::array<int,5> 的 aData,轉換成 std::tuple<int,int,int,int,int> 了~
這邊之所以把函式拆成 a2t() 和 a2t_impl() 兩部分的原因,主要是因為 std::integer_sequence 基本上還是需要當作函式的引數傳入(a2t_impl() 的部分);而如果要考慮使用上的便利性的話,還是應該寫在函式裡處理掉(a2t() 的部分)。
如此一來,在外部呼叫的時候,就只需要呼叫 a2t()、而不用管 std::integer_sequence 的東西了~
而另一種應用,則還可以用來把 std::tuple 展開、當作函式的引數傳進去;例如下面的函式,就是一種可能性的實作:
template<typename FUNC, typename PARA, std::size_t... I> auto call_impl(FUNC& f, const PARA& a, std::index_sequence<I...>) { return f(std::get<I>(a)...); } template<typename FUNC, typename PARA> auto call(FUNC& f, const PARA& a) { static constexpr auto t_count = std::tuple_size<PARA>::value; return call_impl(f, a, std::make_index_sequence<t_count>()); }
比如說想要把 std::tuple 當作引數、呼叫
bool test_func( int a, float b )
這樣的函式的話,可以寫成:
std::tuple<int, float> p = std::make_tuple(1, 1.0f); bool res = call(test_func, p);
而如果搭配之前《C++11 的「…」:Parameter Pack》中提到的 make_tuple_of_params()、自動提取出函式的引數成 std::tuple 的形式的話,應該會有一些特別的玩法吧?
這篇大概就這樣了。
老實說,Heresy 也還不知道這東西到底能拿來幹嘛?就姑且先記錄一下,搞不好以後會有用了。
我猜想,这东西的用途就在std::bind这类函数中。
C++的variadic 参数的展开的时候,是依赖于函数参数调用顺序的,也就是和编译器相关的。
template
void dummy_wrapper(TList… tlist)
{
};
template
T unpacker(const T t)
{
std::cout << '[' << t << ']';
return t;
}
template
void write_line(const ArgsList& …datalist)
{
dummy_wrapper(unpacker(datalist)…);
std::cout << '\n';
}
//这儿的函数输出都是颠倒的,可以认为VC++对于参数展开的顺序都是从右向左,
int test_variadic(int argc,char* argv[])
{
write_line(1,"–","2.2.2","–",3.0);
}
VC++输出的的是[3][–][2.2.2][–][1]
所以大部分variadic 参数的展开是用递归的方式。避免这个问题。
但std::bind这类方法没办法这样用递归展开的。就必须利用tuple作为中转,而且tuple的初始化应该是逗号运算符。顺序是和参数保证一致的。
make_index_sequence的用途就是从tuple中重新展开参数序列。
感谢博主的文章。受益匪浅。
讚讚
恩,或許吧。
不過老實說,個人真的和 std::bind() 不熟就是了。
通常會用 lambda 取代 XD
讚讚
[…] 針對「Parameter Pack」的部分,大概就先記錄到這邊了。 而下一篇的《C++14 編譯階段的整數序列的 integer_sequence》基本上算是有點相關的文章,有興趣的話也可以順便看看。 […]
讚讚