C++14 編譯階段的整數序列的 integer_sequence

| | 0 Comments| 09:12
Categories:

這篇雖然是在講 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 也還不知道這東西到底能拿來幹嘛?就姑且先記錄一下,搞不好以後會有用了。

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *