可变参数模板数据结构

Version >= C++ 14

定义具有在编译时定义的可变数量和类型的数据成员的类或结构通常很有用。规范示例是 std::tuple,但有时需要定义自己的自定义结构。下面是一个使用复合定义结构的示例(而不是像 std::tuple 那样的继承。从常规(空)定义开始,它也作为后续特化中的重新提示终止的基本情况:

template<typename ... T>
struct DataStructure {};

这已经允许我们定义一个空结构 DataStructure<> data,虽然它还不是很有用。

接下来是递归案例专业化:

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;                                
    DataStructure<Rest ... > rest;
};

现在,我们可以创建任意数据结构,例如 DataStructure<int, float, std::string> data(1, 2.1, "hello")

发生什么了?首先,请注意这是一个专业化,其要求是至少存在一个可变参数模板参数(即上面的 T),而不关心包装 Rest 的具体构成。知道 T 存在允许定义其数据成员 first。其余数据以递归方式打包为 DataStructure<Rest ... > rest。构造函数启动这两个成员,包括对 rest 成员的递归构造函数调用。

为了更好地理解这一点,我们可以通过一个例子:假设你有一个声明 DataStructure<int, float> data。声明首先与专业化相匹配,产生一个包含 int firstDataStructure<float> rest 数据成员的结构。rest 定义再次与此专业化相匹配,创建了自己的 float firstDataStructure<> rest 成员。最后,这个最后的 rest 与基本情况定义相匹配,产生一个空结构。

你可以将其可视化如下:

DataStructure<int, float>
   -> int first
   -> DataStructure<float> rest
         -> float first
         -> DataStructure<> rest
              -> (empty)

现在我们有了数据结构,但它并不是非常有用,因为我们无法轻松访问各个数据元素(例如,要访问 DataStructure<int, float, std::string> data 的最后一个成员,我们必须使用 data.rest.rest.first,这不是用户友好的)。所以我们为它添加一个 get 方法(仅在专业化中需要,因为基础结构结构没有数据到 get):

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    ...
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
    ...
};

正如你所看到的,这个 get 成员函数本身是模板化的 - 这次是在需要的成员的索引上(因此使用可以像 data.get<1>(),类似于 std::tuple)。实际工作是由辅助类 GetHelper 中的静态函数完成的。我们无法直接在 DataStructureget 中定义所需功能的原因是因为(我们将很快看到)我们需要专注于 idx - 但是如果不专门化包含类模板,则不可能专门化模板成员函数。另请注意,使用 C++ 14 风格的 auto 使我们的生活变得更加简单,否则我们需要一个非常复杂的返回类型表达式。

所以关于帮助类。这次我们需要一个空的前向声明和两个专业化。首先是声明:

template<size_t idx, typename T>
struct GetHelper;

现在基础情况(当 idx==0)。在这种情况下,我们只返回 first 成员:

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

在递归的情况下,我们减少 idx 并为 rest 成员调用 GetHelper

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

通过一个例子,假设我们有 DataStructure<int, float> data,我们需要 data.get<1>()。这将调用 GetHelper<1, DataStructure<int, float>>::get(data)(第二个专业化),后者又调用 GetHelper<0, DataStructure<float>>::get(data.rest),最终返回(通过第一个专业化,因为现在 idx 为 0)data.rest.first

就是这样了! 以下是整个功能代码,在 main 函数中使用了一些示例:

#include <iostream>

template<size_t idx, typename T>
struct GetHelper;

template<typename ... T>
struct DataStructure
{
};

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;
    DataStructure<Rest ... > rest;
    
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
};

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

int main()
{
    DataStructure<int, float, std::string> data(1, 2.1, "Hello");
        
    std::cout << data.get<0>() << std::endl;
    std::cout << data.get<1>() << std::endl;
    std::cout << data.get<2>() << std::endl;
    
    return 0;
}