在标准库中为集合初始化性类型特征?

时间:2017-12-19 08:32:01

标签: c++ c++17 typetraits aggregate-initialization

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

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

是否有类似的标准库类型特征会检查聚合初始化是否有效,即MyClass{int, 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...>这样的类型特征会派上用场。

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

1 个答案:

答案 0 :(得分:2)

从评论中的讨论和浏览C ++参考,似乎既没有聚合初始化也没有列表初始化的标准库类型特征,至少在C ++ 17之前。< / p>

评论中强调,list initializability一般(Class{arg1, 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 e1{8}; // aggregate initialization :)
    point e2{e1}; // this is not aggregate initialization!
}

要禁止这种列表初始化,我们可以利用聚合不能拥有自定义(即用户提供的)构造函数的事实,因此非聚合初始化必须只有一个参数,而Class{arg}将满足{{1 }}

幸运的是,we can't have a member variable of the same type as its enclosing class,所以以下内容无效:

std::is_same_v<Class, std::decay_t<decltype(arg)>>

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

struct point {
    point x;
};

虽然不寻常,this code is valid by the standard。我没有检测此案例的解决方案,并确定struct point { point& x; }; 是使用point类型的对象进行聚合初始化。

忽略上面的警告(很少需要使用这种类型),我们可以设计一个有效的解决方案:

point&

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