struct universal_type {
std::size_t ignore;
template<class T>
constexpr operator T& () const;
};
// `constructible` has three overloads which can be used to determine
// if T can be aggregate initlaized with a given number of arguments.
// Can we aggregate initialize T with no arguements?
template<class T, class U = decltype(T{})>
constexpr bool constructible(std::index_sequence<>) {
return true;
};
// Can we aggregate initialize T with sizeof...(Ix) + 1 arguments?
template<class T, size_t I, size_t... Ix,
class U = decltype(T{universal_type{I}, universal_type{Ix}...})>
constexpr bool constructible(std::index_sequence<I, Ix...>) {
return true;
};
// If neither of the other overloads are choosen, then we must not be
// able to aggregate initialize T with sizeof...(Ix) arguments.
template<class T, size_t... Ix>
constexpr bool constructible(std::index_sequence<Ix...>) {
return false;
};
// Wrapper for containing field types.
template<class... Ts>
struct aggr_field_list {
using type = std::tuple<Ts...>;
};
template<class T, size_t N>
struct aggr_field_type_impl;
template<class T>
struct aggr_field_type_impl<T, 0> {
static auto ignore() { return aggr_field_list<>{}; }
using type = decltype(ignore());
};
template<class T>
struct aggr_field_type_impl<T, 1> {
static auto ignore() {
T *x = nullptr; auto [a] = *x;
return aggr_field_list<decltype(a)>{};
}
using type = decltype(ignore());
};
template<class T>
struct aggr_field_type_impl<T, 2> {
static auto ignore() {
T *x = nullptr; auto [a, b] = *x;
return aggr_field_list<decltype(a), decltype(b)>{};
}
using type = decltype(ignore());
};
template<class T>
struct aggr_field_type_impl<T, 3> {
static auto ignore() {
T *x = nullptr; auto [a, b, c] = *x;
return aggr_field_list<decltype(a), decltype(b), decltype(c)>{};
}
using type = decltype(ignore());
};
template<class T, size_t N = aggr_field_count_v<T>>
using aggr_field_types = typename aggr_field_type_impl<T, N>::type::type;
我们可以对Foo和Bar做出以下Assert。
// Foo members should have types char, int, double.
using FooTypes = aggr_field_types<Foo>;
static_assert(std::is_same_v<std::tuple_element_t<0, FooTypes>, char>);
static_assert(std::is_same_v<std::tuple_element_t<1, FooTypes>, int>);
static_assert(std::is_same_v<std::tuple_element_t<2, FooTypes>, double>);
// Bar members should have type int*.
using BarTypes = aggr_field_types<Bar>;
static_assert(std::is_same_v<std::tuple_element_t<0, BarTypes>, int*>);
2条答案
按热度按时间uz75evzq1#
对于项1)(标量而不是指针),可以使用以下:
对于第2项)(带指针成员的结构),我认为这是不可能的,因为C++必须支持反射才能在编译时遍历结构的成员。
对于结构体,最好的方法可能是使用
std::is_trivially_copyable
,但这不会检测到指针成员。lawou6xi2#
TLDR;
使用PFR Library,它可以作为Boost的一部分使用,也可以单独作为Header使用。他们使用一些非常聪明的符合标准的元编程来推导结构中的类型(可能是嵌套的),并为这些结构提供一个类似元组的接口。
DIY
因为你只是要求增强类型需求,所以你可以不需要库中的所有机器,它也支持运行时元组类访问。下面是一个简单的大纲,你可以如何去完成这项任务。
您可以在GitHub上找到完整的代码和构建说明。该代码适用于基本示例,但可能存在一些错误和其他缺点,可以改进,因为代码只是一个大纲。
我们开发过程的最终产品将是下面的模板,如果
T
是标量而不是指针,或者如果T
是具有此类成员的(可能是嵌套的)结构,则该模板将返回true
。示例结构
给定示例结构,我们希望能够编写以下Assert。
测试聚合初始化器
下面的函数
constructible
的重载家族允许我们在编译时确定具有特定数量参数的聚合初始化对于我们的目标类型T
是否有效。我们可以使用示例结构
Foo
测试constructible
,并看到聚合初始化最多使用三个参数成功(正如预期的那样,因为它有三个成员)。字段数
我们知道,在每个字段都是一个位的情况下,目标类型
T
的最大可能成员数是sizeof(T) * CHAR_BIT
。我们可以从这个最大值开始,用下面的结构体递归到零,以确定T
接受的聚合初始化器的最大数量,并将其作为字段计数返回。我们可以Assert
Foo
有三个字段,Bar
有一个字段。字段类型
我们可以使用结构化绑定将类型提取为实际上没有具体化的元组类型。我只在结构中包含了最多3个成员的专门化。这是算法中唯一受代码限制的部分,因为您必须手动编写结构化绑定(即没有元编程技巧使其对任意N起作用)。我想你可以用快门来拍一个宏,但这可能是一种异端邪说。
我们可以对
Foo
和Bar
做出以下Assert。申请条件
最后,我们可以应用感兴趣的标准,即我们希望能够识别标量类型(除了指针)和(可能是嵌套的)结构。现在我们已经有了所有的工具,这部分是直接的元编程。
而且,经过太多的准备工作,我们可以做出相关的Assert。
我也很惊讶这是可能的。