可變引數模板資料結構

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