0%

Expansion Statements in C++26

假設我們現在處理struct中每個field的時候,該怎麼做才好

Pre C++20

Boost PFR提供了一個方案

1
2
3
4
struct my_struct { int i; short s; };
int sum = 0;
boost::pfr::for_each_field(my_struct{20, 22}, [&sum](const auto& field) { sum += field; });
assert(sum == 42);

不過還是有它的問題

  • 需要Boost PFR,增加Dependency overhead
  • 雖然看起來像迴圈,不過不能進行break或continue的控制

Tuple & std::make_index_sequence

如果把問題簡化一下,只支援tuple的話,可以寫成

1
2
3
4
5
6
7
8
9
10
11
template <typename... args_t, typename Func>
auto traverse_tuple(std::tuple<args_t...>& t, Func func) {
[&]<size_t... Is>(std::index_sequence<Is...>){
([&](auto&& v){
func(v);
}(std::get<Is>(t)), ...);
}(std::make_index_sequence<sizeof...(args_t)>{});
}
std::tuple<int, short> v(20, 22);
int sum = 0;
traverse_tuple(v, [&](auto v) { sum += v; });

雖然減少了對Boost PFR的依賴
不過問題的複雜度就變成了

  • 將struct變成tuple這件事,處理一般性的struct是很難的一件事,直到C++26 Reflection來到
  • 一樣不能做break 或 continue

C++26 Reflection

在還沒有Expansion Statements之前,Reflection論文中就提供了一種變通的方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace __impl {
template<auto... vals>
struct replicator_type {
template<typename F>
constexpr void operator>>(F body) const {
(body.template operator()<vals>(), ...);
}
};
template<auto... vals>
replicator_type<vals...> replicator = {};
}

template<typename R>
consteval auto expand(R range) {
std::vector<std::meta::info> args;
for (auto r : range) {
args.push_back(std::meta::reflect_constant(r));
}
return substitute(^^__impl::replicator, args);
}
my_struct v { 20, 22 };
int sum = 0;
[: expand(nonstatic_data_members_of(^^my_struct, std::meta::access_context::current())) :] >> [&]<auto e>() {
sum += v.[: e :];
};

雖然可以用,不過
- 難以理解
- 還是不能break 或 continue

Expansion Statements

終於到本文重點了,就當作是一般的for loop就好了

1
2
3
4
5
6
7
8
int main() {
    my_struct v {20, 22};
    int sum = 0;
    template for (auto elem : v) {
        if (elem == 20) continue;
        sum += elem;
    }
}

Reference