Boost hana:強大的 compile-time library(一)

Share on:

本文內容採用創用 CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款授權.

最近因為各種原因發現了 boost hana 這個 library,haha 是 boost 一個 meta programming 的 library。Hana 運用了 C++14 的功能,具有相當優秀的編譯期速度,且編寫起來相對直觀,甚至可以有 macro 把對 struct 的每個物件做 iteration,非常的強大。這系列的文章將會包含 hana 設計的發想來源、一些使用方式以及如何對 struct 的每個物件做 iteration。

經典作法:template specialization

在 C++ 裡面,有一種空的 class 可以拿來承載 type 或是 compile time constant (constexpr) 的資訊。透過 template specialization,可以拿這些 class 做運算之後,得到新的 class,再從裡面拿出需要的 constexpr 進一步運用。一個最常見這類 class 的就是 C++ 之後 11 標準化的 integral_constant

1#include <type_traits>
2template<class T, T v>
3struct integral_constant;
這類 class 裡面沒有任何的 data member,一般作為傳遞、計算 compile time 的資訊使用(這個例子中,資訊就是指 Tv)。其 size 爲 1(因為 C++ 要求 size 不能為 0),因此,雖然 programmer 可以 instantiate 這個 class,但是好像用處也不大:
1cout << sizeof(integral_constant<int, 1>) << endl;
2integral_constant<int, 1> useless{};
下面這兩段程式碼透過 template specialization,搭配 C++11 的 constexpr 可以把兩個 type 相加組成一個新的 type,再把這個新的 type 中的 value 取出來:
 1// vallina template
 2template<class A, class B>
 3struct add_integral {};
 4// specialized template
 5template<int a, int b>
 6struct add_integral<
 7   integral_constant<int, a>,
 8   integral_constant<int, b>
 9> {
10   constexpr int value = a+b;
11};
如下所示,可以看到把 value 取出來之後,我們可以建立新的 integral_constant,繼續進行運算。
 1// x = 4
 2constexpr int x = add_integral<
 3   integral_constant<int, 1>,
 4   integral_constant<int, 3>
 5>::value;
 6// y = 8
 7constexpr int y = add_integral<
 8   integral_constant<int, x>,
 9   integral_constant<int, x>
10>::value;
這樣雖然可以作到 compile time 的加法,這邊有人可能會有一點疑問,為什麼我們明明有 constexpr 了,還需要 integral_constant 這種囉唆的東西呢?這件事情我們 delay 到下下段討論,先來講 boost hana 的一些設計基本邏輯。
1constexpr int a = 1;
2constexpr int b = 1;
3constexpr int c = a+b;
4constexpr int d = c+c;

Hana 的作法:function overload

前面提到,雖然 programmer 可以 instantiate 一個 integral_constant,但是好像用處也不大,但是 hana 的想法卻是不同。hana 發現可以用這個 object 搭配 overload 來做運算,首先我們需要一個對 integral_constant 操作的 function,甚至可以定義一般的 operator+

1template<int a, int b>
2integral_constant<int, a+b> operator+(
3   integral_constant<int, a>,
4   integral_constant<int, b>
5) {
6   return integral_constant<int, a+b>{};
7}
如此一來,我們可以一直 create 空的物件,而且「每個物件本身的型別都帶有運算過的資訊」,像是下面最後一步 decltype(d) 才把型別資訊 value 取出來用。
1integral_constant<int, 1> a{};
2integral_constant<int, 3> b{};
3auto c = a+b; // c is of type integral_constant<int, 3>
4auto d = c+c; // d is of type integral_constant<int, 8>
5constexpr int value = decltype(d)::value; // value = 8
這樣做的一個好處是,相較於 template specialization,寫起來非常直觀。其中最主要的區別就是,template specialization 是針對型別運算,hana 的作法是針對物件運算,這些物件本身不佔任何空間,但是他的型別本身就帶有我們要的資訊。

為什麼需要 integral_constant?

這邊回來講一下前面延後講的 integral_constant 的用法,一個常見的用法是把他當作一個 tag,這樣我們就可以在 compile time 去決定要 call 哪個版本的函數,例如說下面這個例子,f2f3 的函數回傳的型別不同,所以沒辦法編譯成功。

 1array<int, 2> f2() { return {99,99}; }
 2array<int, 3> f3() { return {1,1,1}; }
 3template<int x>
 4array<int, x> f() {
 5   if (x == 2) {
 6      return f2();
 7   } else if (x == 3) {
 8      return f3();
 9   }
10}
即使 template 參數 xconstexpr,還是不能編譯,integral_constant 就能派上用場,用 overload 的方式來選出想要的函數。
1array<int, 2> f_inner(integral_constant<int, 2>) { return {99,99}; }
2array<int, 3> f_inner(integral_constant<int, 3>) { return {1,1,1}; }
3template<int x>
4array<int, x> f() {
5   return f_inner(integral_constant<int, x>{});
6}
(註:不過 C++17 可以用 if constexpr 輕鬆達成)
1template<int x>
2array<int, x> f() {
3   if constexpr (x == 2) {
4      return f2();
5   } else if constexpr (x == 3) {
6      return f3();
7   }
8}

總結

其實這篇文章還沒講到 boost hana 到底可以作到哪些神奇的事情,因為前情提要不小心打太多了,就從下一篇開始講吧。


  1. Boost hana:強大的 compile-time library(一)