c++ 接受标量(指针除外)和结构的泛型类

nqwrtyyt  于 2023-05-08  发布在  其他
关注(0)|答案(2)|浏览(138)

如何强制泛型类只接受:
1.标量,指针除外;
1.以及只包含****类型1成员的结构。

template < typename T >
class C
{
  static_assert(what_condition, "wrong argument");
  //...
};
uz75evzq

uz75evzq1#

对于项1)(标量而不是指针),可以使用以下:

static_assert(!std::is_pointer<T>() && 
               std::is_scalar<T>(), "wrong argument");

对于第2项)(带指针成员的结构),我认为这是不可能的,因为C++必须支持反射才能在编译时遍历结构的成员。
对于结构体,最好的方法可能是使用std::is_trivially_copyable,但这不会检测到指针成员。

lawou6xi

lawou6xi2#

TLDR;

使用PFR Library,它可以作为Boost的一部分使用,也可以单独作为Header使用。他们使用一些非常聪明的符合标准的元编程来推导结构中的类型(可能是嵌套的),并为这些结构提供一个类似元组的接口。

DIY

因为你只是要求增强类型需求,所以你可以不需要库中的所有机器,它也支持运行时元组类访问。下面是一个简单的大纲,你可以如何去完成这项任务。
您可以在GitHub上找到完整的代码和构建说明。该代码适用于基本示例,但可能存在一些错误和其他缺点,可以改进,因为代码只是一个大纲。
我们开发过程的最终产品将是下面的模板,如果T是标量而不是指针,或者如果T是具有此类成员的(可能是嵌套的)结构,则该模板将返回true

template<class T>
inline constexpr bool criteria_v;

示例结构

struct Foo {
    char a;
    int b;
    double c;
};

struct Bar {
    int *ptr;
};

给定示例结构,我们希望能够编写以下Assert。

static_assert(criteria_v<int>);
static_assert(not criteria_v<int*>);
static_assert(criteria_v<Foo>);
static_assert(not criteria_v<Bar>);

测试聚合初始化器

下面的函数constructible的重载家族允许我们在编译时确定具有特定数量参数的聚合初始化对于我们的目标类型T是否有效。

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;
};

我们可以使用示例结构Foo测试constructible,并看到聚合初始化最多使用三个参数成功(正如预期的那样,因为它有三个成员)。

// Foo can be initlaized with 0, 1, 2, or 3 arguments.
static_assert(constructible<Foo>(std::index_sequence<>{}));
static_assert(constructible<Foo>(std::index_sequence<1>{}));
static_assert(constructible<Foo>(std::index_sequence<1, 2>{}));
static_assert(constructible<Foo>(std::index_sequence<1, 2, 3>{}));
static_assert(not constructible<Foo>(std::index_sequence<1, 2, 3, 4>{}));
static_assert(not constructible<Foo>(std::index_sequence<1, 2, 3, 4, 5>{}));

字段数

我们知道,在每个字段都是一个位的情况下,目标类型T的最大可能成员数是sizeof(T) * CHAR_BIT。我们可以从这个最大值开始,用下面的结构体递归到零,以确定T接受的聚合初始化器的最大数量,并将其作为字段计数返回。

// Returns the number of members of T. Utilizes the contructible
// overloads as helpers.
template<class T>
struct aggr_field_count {
    template<size_t N>
    struct impl;

    template<size_t N> requires (N == 0)
    struct impl<N> { static constexpr size_t value = 0; };

    template<size_t N> requires (N > 0)
    struct impl<N> {
    static constexpr bool good = constructible<T>(std::make_index_sequence<N>{});
    static constexpr size_t value = good ? N : impl<N - 1>::value;
    };

    static constexpr size_t value = impl<sizeof(T)>::value;
};

template<class T>
inline constexpr auto aggr_field_count_v = aggr_field_count<T>::value;

我们可以AssertFoo有三个字段,Bar有一个字段。

// Foo has 3 members and Bar has one member.
static_assert(aggr_field_count_v<Foo> == 3);
static_assert(aggr_field_count_v<Bar> == 1);

字段类型

我们可以使用结构化绑定将类型提取为实际上没有具体化的元组类型。我只在结构中包含了最多3个成员的专门化。这是算法中唯一受代码限制的部分,因为您必须手动编写结构化绑定(即没有元编程技巧使其对任意N起作用)。我想你可以用快门来拍一个宏,但这可能是一种异端邪说。

// 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;

我们可以对FooBar做出以下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*>);

申请条件

最后,我们可以应用感兴趣的标准,即我们希望能够识别标量类型(除了指针)和(可能是嵌套的)结构。现在我们已经有了所有的工具,这部分是直接的元编程。

template<class T>
struct criteria_impl;

template<class T> requires (not std::is_aggregate_v<T>)
struct criteria_impl<T> {
    static constexpr bool value = std::is_scalar_v<T> and not std::is_pointer_v<T>;
};

template<class T> requires (std::is_aggregate_v<T>)
struct criteria_impl<T> {
    using U = aggr_field_types<T>;
    static constexpr bool value = criteria_impl<U>::value;
};

template<class... Ts>
struct criteria_impl<std::tuple<Ts...>> {
    static constexpr bool value = (criteria_impl<Ts>::value and ...);
};

template<class T>
inline constexpr bool criteria_v = criteria_impl<T>::value;

而且,经过太多的准备工作,我们可以做出相关的Assert。

static_assert(criteria_v<int>);
static_assert(not criteria_v<int*>);
static_assert(criteria_v<Foo>);
static_assert(not criteria_v<Bar>);

我也很惊讶这是可能的。

相关问题