标准库中聚合可初始化性的类型特征?
Type trait for aggregate initializability in the standard library?
C++ 标准库有 std::is_constructible<Class, T...>
检查是否可以从给定类型作为参数构造 class。
例如,如果我有一个 class MyClass
,它有一个构造函数 MyClass(int, char)
,那么 std::is_constructible<MyClass, int, char>::value
将是 true
.
是否有类似的标准库类型特征来检查聚合初始化是否有效,即 MyClass{int, char}
格式正确且 returns 是 MyClass
?
我的用例:
我想编写一个函数模板,使用聚合初始化将 std::tuple
转换为(通常是 POD)class,具有以下签名:
template <typename Class, typename... T>
inline Class to_struct(std::tuple<T...>&& tp);
为了防止用户使用带有无效 Class
的函数,我可以在这个函数中写一个 static_assert
来检查给定的 tp
参数是否具有以下类型可转换为 Class
的成员。似乎像 is_aggregate_initializable<Class, T...>
这样的类型特征会派上用场。
我可以自己实现这个特性,但仅供参考,标准库中是否有我忽略的特性,或者即将成为标准库一部分的特性?
从评论中的讨论和浏览 C++ 参考资料来看,似乎 对于聚合可初始化性和列表可初始化性都没有标准库类型特征,至少 C ++17.
在评论中强调了 list initializability in general (Class{arg1, arg2, ...}
) and aggregate initializability.
之间的区别
List initializability(特别是 direct list 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), and we have a C++17 trait std::is_aggregate
的解释。
然而,这并不意味着仅仅因为类型是聚合类型,通常的直接列表初始化就无效。仍然允许匹配构造函数的正常列表初始化。例如,以下编译:
struct point {
int x,y;
};
int main() {
point e1{8}; // aggregate initialization :)
point e2{e1}; // this is not aggregate initialization!
}
要禁止这种列表初始化,我们可以利用聚合不能具有自定义(即用户提供的)构造函数这一事实,因此非聚合初始化必须只有一个参数并且 Class{arg}
将满足 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;
它看起来不太好,但确实可以正常工作。
C++ 标准库有 std::is_constructible<Class, T...>
检查是否可以从给定类型作为参数构造 class。
例如,如果我有一个 class MyClass
,它有一个构造函数 MyClass(int, char)
,那么 std::is_constructible<MyClass, int, char>::value
将是 true
.
是否有类似的标准库类型特征来检查聚合初始化是否有效,即 MyClass{int, char}
格式正确且 returns 是 MyClass
?
我的用例:
我想编写一个函数模板,使用聚合初始化将 std::tuple
转换为(通常是 POD)class,具有以下签名:
template <typename Class, typename... T>
inline Class to_struct(std::tuple<T...>&& tp);
为了防止用户使用带有无效 Class
的函数,我可以在这个函数中写一个 static_assert
来检查给定的 tp
参数是否具有以下类型可转换为 Class
的成员。似乎像 is_aggregate_initializable<Class, T...>
这样的类型特征会派上用场。
我可以自己实现这个特性,但仅供参考,标准库中是否有我忽略的特性,或者即将成为标准库一部分的特性?
从评论中的讨论和浏览 C++ 参考资料来看,似乎 对于聚合可初始化性和列表可初始化性都没有标准库类型特征,至少 C ++17.
在评论中强调了 list initializability in general (Class{arg1, arg2, ...}
) and aggregate initializability.
List initializability(特别是 direct list 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), and we have a C++17 trait std::is_aggregate
的解释。
然而,这并不意味着仅仅因为类型是聚合类型,通常的直接列表初始化就无效。仍然允许匹配构造函数的正常列表初始化。例如,以下编译:
struct point {
int x,y;
};
int main() {
point e1{8}; // aggregate initialization :)
point e2{e1}; // this is not aggregate initialization!
}
要禁止这种列表初始化,我们可以利用聚合不能具有自定义(即用户提供的)构造函数这一事实,因此非聚合初始化必须只有一个参数并且 Class{arg}
将满足 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;
它看起来不太好,但确实可以正常工作。