這是一種常見的code pattern
1 2 3 4 5 6
| template <size_t length> void for_loop_do() { for (size_t i = 0; i < length; i++) { do(i); } }
|
不過既然我們在compile-time知道length的值了,自然想要unroll loop body,變成類似這樣
1 2 3 4 5 6
| void unroll_do() { do(0); do(1); do(2); .... }
|
直覺的想法就是costexpr for
1 2 3 4 5 6
| template <size_t length> void for_loop_do() { constexpr for (size_t i = 0; i < length; i++) { do(i); } }
|
不過自然沒這語法,只好用其他方法繞過,解決的方法也不只一種,提出兩種比較常用的
Version 1: Recursive + if constexpr
學過資料結構的話,知道迴圈可以用遞洄來模擬
加上C++17的if constexpr
可以很容易蹶決這問題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template <size_t index, size_t boundary, typename Func>
void constexpr_for_impl(Func func) { if constexpr (index == boundary) { return; } else { func(index); constexpr_for_impl<index + 1, boundary>(func); } }
template <size_t boundary, typename Func> void constexpr_for(Func func) { constexpr_for_impl<0, boundary>(func); }
int main() { constexpr_for<3>([](size_t index) { printf("llu\n", index); }); }
|
Version 2: integer_sequence
C++14之後,引進了一個helper class integer_sequence
,將常數納入Type當中
這很少直接拿來用,通常只有操作tuple時才會使用,不過要模擬constexpr for這個特色,這功能少不了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <utility>
template <typename T, T... ints> void print_sequence(std::integer_sequence<T, ints...> int_seq) { std::cout << "The sequence of size " << int_seq.size() << ": "; ((std::cout << ints << ' '), ...); std::cout << '\n'; }
int main() { print_sequence(std::integer_sequence<unsigned, 9, 2, 5, 1, 9, 1, 6>{}); }
|
我們想要的就是有一個類似std::integer_sequence<size_t, 0, 1, ..., n>
這樣的sequence,這時候另外一個helper type make_integer_sequence
登場了
以下是上一個範例的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <utility>
template <typename T, T... ints> void print_sequence(std::integer_sequence<T, ints...> int_seq) { std::cout << "The sequence of size " << int_seq.size() << ": "; ((std::cout << ints << ' '), ...); std::cout << '\n'; }
int main() { print_sequence(std::make_integer_sequence<int, 12>{}); }
|
這就是constexpr for的核心觀念
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template <std::size_t... Is, typename Func> void constexpr_for_impl(std::index_sequence<Is...>, Func func) { (func(Is), ...); }
template <size_t boundary, typename Func> void constexpr_for(Func func) { constexpr_for_impl(std::make_index_sequence<boundary>{}, func); }
int main() { constexpr_for<3>([](size_t index) { printf("llu\n", index); }); }
|
C++20之後,有了 Template Lambdas
,因此我們可以進一步這樣寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| template <size_t boundary, typename Func> void constexpr_for(Func func) { [&] <size_t... Is> (std::index_sequence<Is...>) { ([&] <size_t I> (std::integral_constant<size_t, I>) { func(I); } (std::integral_constant<size_t, Is>{}), ...); } (std::make_index_sequence<boundary>{}); }
int main() { constexpr_for<3>([](size_t index) { printf("llu\n", index); }); }
|
Conclusion
模擬只是模擬,當for loop加入了continue
,break
,return
之後,上面的方法就會變得很複雜,只適合簡單的應用