8

C++ 标准库必须std::is_constructible<Class, T...>检查是否可以从给定类型作为参数构造类。

例如,如果我有一个MyClass具有构造函数的类MyClass(int, char),那么std::is_constructible<MyClass, int, char>::value将是true.

是否有类似的标准库类型特征将检查聚合初始化是否有效,即格式正确MyClass{int, char}并返回MyClass?

我的用例:

我想编写一个函数模板,std::tuple使用聚合初始化将 a 转换为(通常是 POD)类,具有以下签名:

template <typename Class, typename... T>
inline Class to_struct(std::tuple<T...>&& tp);

为了防止用户使用无效的这个函数Class,我可以在这个函数内部写一个static_assert来检查给定的tp参数是否具有可转换为成员的类型Class。似乎一种类型特征is_aggregate_initializable<Class, T...>会派上用场。

我可以推出我自己的这个特性的实现,但只是为了提供信息,标准库中是否有我忽略的这样一个特性,或者很快就会成为标准库的一部分?

4

1 回答 1

4

从评论中的讨论和浏览 C++ 参考,似乎有一个标准库类型特征既不是聚合初始化也不是列表初始化,至少到 C++17。

评论中强调了一般列表可初始化性( Class{arg1, arg2, ...}) 和聚合可初始化性之间的区别。

列表可初始化性(特别是直接列表可初始化性)更容易为其编写类型特征,因为此特征仅取决于特定语法的有效性。对于我测试是否可以从元组的元素构造结构的用例,直接列表可初始化性似乎更合适。

实现此特征的一种可能方法(使用适当的 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++ 参考的解释),并且我们有一个 C++17 特征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)>>.

幸运的是,我们不能拥有与其封闭类相同类型的成员变量,因此以下内容无效:

struct point {
    point x;
};

对此有一个警告:允许对同一对象的引用类型,因为成员引用可能是不完整的类型(GCC、Clang 和 MSVC 都接受这一点而没有任何警告):

struct point {
    point& x;
};

虽然不寻常,但此代码在标准中是有效的。我没有解决方案来检测这种情况并确定它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;

它看起来不是很好,但确实按预期运行。

于 2017-12-20T12:38:09.517 回答