不幸的是,从C++20开始,结构化绑定不支持参数包。
不过,您可以通过为每个不同数量的参数提供一个实现来解决这个问题(如您的示例中所示)
1、使用
if constexpr
通过使用
如果constexpr
-如果条件不为true,则会丢弃整个语句(对于未实例化的模板化实体,也就是说,它不必为给定的模板类型进行编译)。
auto
返回类型仅从未丢弃的返回语句推断返回类型,因此您可以使用
如果constexpr
更改函数的返回类型。
这包括:
8.5.2 (2) The if statement
如果转换条件的值为false,则第一个子状态
是丢弃的语句
,否则,第二个子语句(如果存在)是丢弃的语句。在封闭模板实体的实例化过程中,如果条件在实例化后不依赖于值,则丢弃的子状态(如果有)
未实例化
。
9.2.9.6.1 (3) Placeholder type specifiers
如果声明的函数返回类型包含占位符类型,则函数的返回类型将从
未丢弃
函数体中的返回语句(如果有)。
这允许您编写
as_tuple
功能如下:
示例:
godbolt
#include <tuple>
#include <concepts>
template<class T>
concept aggregate = std::is_aggregate_v<T>;
struct any_type {
template<class T>
operator T() {}
};
// Count the number of members in an aggregate by (ab-)using
// aggregate initialization
template<aggregate T>
consteval std::size_t count_members(auto ...members) {
if constexpr (!requires { T{ members... }; })
return sizeof...(members) - 1;
else
return count_members<T>(members..., any_type{});
}
template<aggregate T>
constexpr auto as_tuple(T const& data) {
constexpr std::size_t fieldCount = count_members<T>();
if constexpr(fieldCount == 0) {
return std::tuple();
} else if constexpr (fieldCount == 1) {
auto& [m1] = data;
return std::tuple(m1);
} else if constexpr (fieldCount == 2) {
auto& [m1, m2] = data;
return std::tuple(m1, m2);
} else if constexpr (fieldCount == 3) {
auto& [m1, m2, m3] = data;
return std::tuple(m1, m2, m3);
} else if constexpr (fieldCount == 4) {
auto& [m1, m2, m3, m4] = data;
return std::tuple(m1, m2, m3, m4);
} else {
static_assert(fieldCount!=fieldCount, "Too many fields for as_tuple(...)! add more if statements!");
}
}
int main() {
struct toto{ int i; };
constexpr toto t{42};
constexpr auto tup = as_tuple(t);
static_assert(std::same_as<const int, std::tuple_element_t<0, decltype(tup)>>);
static_assert(42 == std::get<0>(tup));
}
那么我们需要做的就是生成
如果constexpr
分支最多可支持最大数量的成员。
注:
如果需要,也可以更换
std::tuple(...)
具有
std::tie(...)
返回对原始成员的引用元组,而不是复制值的元组。(
godbolt example
)
2、生成分支
Boost预处理器
提供了许多方便的宏,我们可以使用它们轻松地生成所需的分支。
在本例中,我们只需要其中的两个宏:
-
BOOST_PP_ENUM_PARAMS
生成变量名称。
示例:
BOOST_PP_ENUM_PARAMS(3, foo)
将扩展到
foo0, foo1, foo2
-
BOOST_PP_REPEAT_FROM_TO
重复调用宏。
示例:
BOOST_PP_REPEAT_FROM_TO(1, 3, FOO, ~)
将扩展到
FOO(z,1,~) FOO(z,2,~) FOO(z,3,~)
这允许我们生成多达255个
如果constexpr
只有几行代码:
Boost PP示例:
godbolt
template<aggregate T>
constexpr auto as_tuple(T& data) {
constexpr std::size_t fieldCount = count_members<T>();
if constexpr(fieldCount == 0) {
return std::tuple();
}
#define AS_TUPLE_STMT(z, n, unused) \
else if constexpr(fieldCount == n) { \
auto& [ BOOST_PP_ENUM_PARAMS(n, m) ] = data; \
return std::tuple( BOOST_PP_ENUM_PARAMS(n, m) ); \
}
BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_LIMIT_REPEAT, AS_TUPLE_STMT, ~)
#undef AS_TUPLE_STMT
else {
static_assert(fieldCount!=fieldCount, "Too many fields for as_tuple(...)! add more if statements!");
}
}
此版本可以将最多包含255个成员的聚合转换为元组。
如果你需要更多,你可以改变
BOOST_PP_LIMIT_MAG
&
BOOST_PP_LIMIT_REPEAT
到512或1024,这将允许您分别处理最多511或1023个成员的聚合。
2.2纯C++宏(无boost)
如果没有boost,您将不得不手动编写大量样板宏代码——不幸的是,这是无法避免的。
此示例使用宏“递归”,使用
deferred expressions
和重复扫描
EXPAND
宏)
C++20还添加了
__VA_OPT__
宏,这使得使用它更容易。
示例:
godbolt
#define NUMBER_SEQ \
50,49,48,47,46,45,44,43,42,41, \
40,39,38,37,36,35,34,33,32,31, \
30,29,28,27,26,25,24,23,22,21, \
20,19,18,17,16,15,14,13,12,11, \
10, 9, 8, 7, 6, 5, 4, 3, 2, 1
#define PARENS ()
#define UNWRAP(...) __VA_ARGS__
#define FIRST(el, ...) el
#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__
#define SEQ_MAP(macro, ...) __VA_OPT__(EXPAND(SEQ_MAP_HELPER(macro, __VA_ARGS__)))
#define SEQ_MAP_HELPER(macro, el, ...) macro(el __VA_OPT__(, __VA_ARGS__)) __VA_OPT__(SEQ_MAP_HELPER_AGAIN PARENS (macro, __VA_ARGS__))
#define SEQ_MAP_HELPER_AGAIN() SEQ_MAP_HELPER
#define EXPANDZ(...) EXPANDZ4(EXPANDZ4(EXPANDZ4(EXPANDZ4(__VA_ARGS__))))
#define EXPANDZ4(...) EXPANDZ3(EXPANDZ3(EXPANDZ3(EXPANDZ3(__VA_ARGS__))))
#define EXPANDZ3(...) EXPANDZ2(EXPANDZ2(EXPANDZ2(EXPANDZ2(__VA_ARGS__))))
#define EXPANDZ2(...) EXPANDZ1(EXPANDZ1(EXPANDZ1(EXPANDZ1(__VA_ARGS__))))
#define EXPANDZ1(...) __VA_ARGS__
#define SEQ_MAPZ(macro, ...) __VA_OPT__(EXPANDZ(SEQ_MAPZ_HELPER(macro, __VA_ARGS__)))
#define SEQ_MAPZ_HELPER(macro, el, ...) macro(el __VA_OPT__(, __VA_ARGS__)) __VA_OPT__(, SEQ_MAPZ_HELPER_AGAIN PARENS (macro, __VA_ARGS__))
#define SEQ_MAPZ_HELPER_AGAIN() SEQ_MAPZ_HELPER
#define ADD_M(x, ...) m##x
#define GEN_BRANCH(...) \
else if constexpr(fieldCount == FIRST(__VA_ARGS__)) { \
auto& [ SEQ_MAPZ(ADD_M, __VA_ARGS__) ] = data; \
return std::tuple( SEQ_MAPZ(ADD_M, __VA_ARGS__) ); \
}
template<aggregate T>
constexpr auto as_tuple(T& data) {
constexpr std::size_t fieldCount = count_members<T>();
if constexpr(fieldCount == 0) {
return std::tuple();
}
SEQ_MAP(GEN_BRANCH, NUMBER_SEQ)
else {
static_assert(fieldCount!=fieldCount, "Too many fields for as_tuple(...)! add more if statements!");
}
}
此版本最多支持50个聚合成员,但您可以通过向
NUMBER_SEQ
并添加更多
EXPANDx
宏(根据需要)(每个
EXPANDx公司
宏您添加的扫描数是原来的四倍,因此您只需要几个扫描)
建议阅读:
2.3编写代码生成器
除了使用宏生成代码之外,您还可以编写一个程序,作为构建设置的一部分,为实际程序生成头。
这使得代码更容易阅读(没有宏骗局),并且您只需为您想要支持的成员数生成一次代码。
示例:
godbolt
#include <iostream>
#include <vector>
int main() {
std::cout << R"(
#include <tuple>
#include <concepts>
template<class T>
concept aggregate = std::is_aggregate_v<T>;
struct any_type {
template<class T>
operator T() {}
};
template<aggregate T>
consteval std::size_t count_members(auto ...members) {
if constexpr (!requires { T{ members... }; })
return sizeof...(members) - 1;
else
return count_members<T>(members..., any_type{});
}
template<aggregate T>
constexpr auto as_tuple(T const& data) {
constexpr std::size_t fieldCount = count_members<T>();
if constexpr(fieldCount == 0) {
return std::tuple();
}
)";
std::string variables;
for(int i = 1; i <= 10; i++) {
if(variables.length() > 0) variables += ", ";
variables += "m" + std::to_string(i);
std::cout
<< " else if constexpr(fieldCount == " << i << ") {\n"
<< " auto& [" << variables << "] = data;\n"
<< " return std::tuple(" << variables << ");\n"
<< " }\n";
}
std::cout
<< " else {\n"
<< " static_assert(fieldCount!=fieldCount, \"Too many fields for as_tuple(...)! add more if statements!\");\n"
<< " }\n"
<< "}"
<< std::endl;
return 0;
}
你可能想退房
增压PFR
-它正是您想要完成的:一个用于任意结构的类似元组的接口:
促进PFR是一个C++14库,用于实现非常基本的反射。它允许您通过索引访问结构元素,并为用户定义的类型提供其他类似std::tuple的方法,而无需宏或样板代码
示例:
godbolt
#include <boost/pfr.hpp>
int main() {
struct foo { char a; int b; };
constexpr foo var{'A', 42};
// converting to tuple
constexpr auto tup = boost::pfr::structure_to_tuple(var);
static_assert(std::same_as<const char, std::tuple_element_t<0, decltype(tup)>>);
static_assert(std::get<0>(tup) == 'A');
static_assert(std::get<1>(tup) == 42);
// direct
static_assert(boost::pfr::get<0>(var) == 'A');
static_assert(boost::pfr::get<1>(var) == 42);
}