标准库中聚合可初始化性的类型特征?
Posted
技术标签:
【中文标题】标准库中聚合可初始化性的类型特征?【英文标题】:Type trait for aggregate initializability in the standard library? 【发布时间】:2018-06-01 15:27:06 【问题描述】:C++ 标准库有std::is_constructible<Class, T...>
来检查是否可以将给定类型作为参数构造类。
例如,如果我有一个类MyClass
,它有一个构造函数MyClass(int, char)
,那么std::is_constructible<MyClass, int, char>::value
将是true
。
是否有类似的标准库类型特征可以检查聚合初始化是否有效,即MyClassint, char
格式正确并返回MyClass
?
我的用例:
我想编写一个函数模板,使用聚合初始化将std::tuple
转换为(通常是 POD)类,具有以下签名:
template <typename Class, typename... T>
inline Class to_struct(std::tuple<T...>&& tp);
为了防止用户使用无效的Class
这个函数,我可以在这个函数内部写一个static_assert
来检查给定的tp
参数是否具有可转换为Class
成员的类型.看起来像is_aggregate_initializable<Class, T...>
这样的类型特征会派上用场。
我可以自行实现这个特性,但仅供参考,标准库中是否存在我忽略的特性,或者即将成为标准库一部分的特性?
【问题讨论】:
老实说,我不明白为什么它会那么有用。std::is_constructible
几乎存在,因此泛型代码,对于像 std::vector<int>
这样的类型,可以避免意外地进行列表初始化。为什么你不能只做MyClass Args...
并且需要关心这是否是一个聚合?
@StoryTeller 在更一般的代码中,只要定义了std::tuple_size
和std::tuple_element
,to_struct()
就应该工作,所以它应该看起来像template <typename Class, typename Tuple> inline Class to_struct(Tuple&& tp);
,具有适当的内部实现函数依靠std::tuple
。在那之后,例如,我可能想要另一个 to_struct()
重载,它将Class
包裹在给定对象周围,无需解包(仅当它无法解包时)。在这种情况下,我需要使用类型特征来限制第一个重载(可能使用 SFINAE 的东西)。
@Bernard:“我可以自己实现这个特性”不,你不能。如果没有 is_aggregate
特征,您将无法区分 aggregate1, 2
working 和 non_aggregate1, 2
working。
@NicolBolas is_aggregate
真的,你想知道T fooa,b,c
是否合法?你关心它是使用普通的构造函数还是聚合初始化?
【参考方案1】:
从 cmets 中的讨论和浏览 C++ 参考资料来看,似乎有一个标准库类型特征既不是聚合可初始化性也不是列表可初始化性,至少在 C++17 之前是这样。
在 cmets 中强调,list initializability(Classarg1, arg2, ...
)和 aggregate initializability 之间存在区别。
列表可初始化性(特别是直接列表可初始化性)更容易编写类型特征,因为该特征仅取决于特定语法的有效性。对于我测试是否可以从元组的元素构造结构的用例,直接列表可初始化性似乎更合适。
实现此特征的一种可能方法(使用适当的 SFINAE)如下:
namespace detail
template <typename Struct, typename = void, typename... T>
struct is_direct_list_initializable_impl : std::false_type ;
template <typename Struct, typename... T>
struct is_direct_list_initializable_impl<Struct, std::void_t<decltype(Struct std::declval<T>()... )>, T...> : std::true_type ;
template <typename Struct, typename... T>
using is_direct_list_initializable = detail::is_direct_list_initializable_impl<Struct, void, T...>;
template<typename Struct, typename... T>
constexpr bool is_direct_list_initializable_v = is_direct_list_initializable<Struct, T...>::value;
然后我们可以通过 is_direct_list_initializable_v<Class, T...>
来测试直接列表的可初始化性。
这也适用于移动语义和完美转发,因为std::declval
遵循完美转发规则。
聚合可初始化性不太简单,但有一个解决方案可以涵盖大多数情况。聚合初始化要求被初始化的类型是聚合(参见 C++ reference on aggregate initialization 的解释),我们有一个 C++17 特征 std::is_aggregate
来检查类型是否为聚合。
但是,这并不意味着仅仅因为类型是聚合,通常的直接列表初始化就会无效。仍然允许匹配构造函数的正常列表初始化。例如下面的编译:
struct point
int x,y;
;
int main()
point e18; // aggregate initialization :)
point e2e1; // this is not aggregate initialization!
为了禁止这种列表初始化,我们可以利用聚合不能有自定义(即用户提供的)构造函数这一事实,因此非聚合初始化必须只有一个参数,Classarg
将满足std::is_same_v<Class, std::decay_t<decltype(arg)>>
。
幸运的是,we can't have a member variable of the same type as its enclosing class,所以以下内容无效:
struct point
point x;
;
对此有一个警告:允许对同一对象的引用类型,因为成员引用可能是不完整的类型(GCC、Clang 和 MSVC 都接受这一点而不会发出任何警告):
struct point
point& x;
;
虽然不寻常,但this code is valid by the standard。我没有解决方案来检测这种情况并确定point
可以使用point&
类型的对象进行聚合初始化。
忽略上面的警告(很少需要使用这种类型),我们可以设计一个可行的解决方案:
template <typename Struct, typename... T>
using is_aggregate_initializable = std::conjunction<std::is_aggregate<Struct>, is_direct_list_initializable<Struct, T...>, std::negation<std::conjunction<std::bool_constant<sizeof...(T) == 1>, std::is_same<std::decay_t<std::tuple_element_t<0, std::tuple<T...>>>, Struct>>>>;
template<typename Struct, typename... T>
constexpr bool is_aggregate_initializable_v = is_aggregate_initializable<Struct, T...>::value;
它看起来不是很好,但确实按预期运行。
【讨论】:
以上是关于标准库中聚合可初始化性的类型特征?的主要内容,如果未能解决你的问题,请参考以下文章