c++ 模板模板引數("Template Template Parameters")

白伟碧一些小心得發表於2024-05-08
#include <iostream>
#include <vector>
#include <list>
 
using namespace std;
 
namespace _nmsp1
{
    // T型別模板引數,代表容器中元素型別
    // Container代表的不是一個型別(不能是一個型別模板引數),而是一個類别範本(類名)
    // Container不叫做型別模板引數,而叫做模板模板引數,表示這個模板引數本身又是一個模板;
    template <
        typename T,
        //typename Container = std::vector
        //template <class> class Container = std::vector        // 這就是一個名為Container(其它名字也行)的模板模板引數;
        template <typename W> typename Container = std::vector  // 另一種寫法,W可以省略,
                                                                // Container如果myclass類别範本中不使用也可以省略,
                                                                // 從而出現了typename後面直接接=的寫法
    >
    class myclass
    {
    public:
        Container<T> myc;
 
    public:
        void func();
        myclass() // 建構函式
        {
            for (int i = 0; i < 10; ++i)
            {
                myc.push_back(i);  // 這幾行程式碼是否正確取決於例項化該類别範本時所提供的模板引數型別
            }
        }
    };
 
    template <
        typename T,
        template <typename W> typename  Container
    >
    void myclass<T, Container>::func()
    {
        cout << "good!" << endl;
    }
 
 
    //-------------------
    template <
        //.....
        template <typename W, W* point> typename Container
    >
    class myclass2
    {
        // W* m_p;  // 錯誤,不可以在這裡使用W(W叫做模板模板引數Container的模板引數)
    };
 
    //--------------------
    template <
        //......
        // Container如果myclass類别範本中不使用也可以省略,
        // 從而出現了typename後面直接接=的寫法
        template <typename W> typename = std::vector 
    >
    class myclass3
    {
 
    };
}
 
// 模板模板引數
// 英文名,Template Template Parameters,即模板引數本身成為模板
//      a) int,型別,簡單型別/內部型別
//      b) vector,list,是C++標準庫中的容器,類别範本(類名),
//              vector<int>或者list<double>就屬於模板例項化的引數稱為型別(類型別);
int main()
{
    _nmsp1::myclass<int, vector> myvectorobj;   // int是容器中的元素型別,vector是容器型別
    _nmsp1::myclass<double, list> mylistobj;    // double是容器中的元素型別,list是容器型別
    _nmsp1::myclass<double, list> mylistobj2;   // double是容器中的元素型別,list是容器型別
    mylistobj2.func();
}

"Template Template Parameters" 是指模板引數本身是一個模板。這種情況在 C++ 中用於定義接受其他模板作為引數的模板,通常用於實現泛型和高階模板程式設計。這種概念在一些高階 C++ 模板設計和超程式設計場景中非常有用。

在 C++ 中,模板引數不僅可以是型別(例如 intfloat 等),也可以是整型常量、指標、引用、甚至是類别範本。這些引數在 C++11 及以後版本中得到了更好的支援,特別是 C++17 及之後。

但是上面的程式碼是有問題的,std::vector/list 是一個模板類,它有兩個模板引數:元素型別和分配器型別。因此,在使用 std::vector 作為模板引數時,你需要提供兩個模板引數,而不是一個。下面是修正後的程式碼:

template <class T,
          template <class, class> class Contain>
class A
{
public:
    void fun()
    {
        for(int i=0; i<5; i++)
        {
            mycon.push_back(i);
        }
    }
    
    void output()
    {
        for(auto& i : mycon)
        {
            cout << i << endl;
        }
    }

private:
    Contain<T, std::allocator<T>> mycon;
};

int main()
{
    A<int, std::vector> v1;
    v1.fun();
    v1.output();
    
    return 0;
}

template <class T>
using Vector=std::vector<T,std::allocator<T>>;

template <class T,
template <class,class> class Contain>
class A
{
public:
 void fun()
 {
   for(int i=0;i<5;i++)
   {
     mycon.push_back(i);
   }
 }
 void output()
 {
   for(auto& i:mycon)
   {
     cout<<i<<endl;
   }
 }

private:
  Contain<T,std::allocator<T>> mycon;
};
int main()
{
  A<int,std::vector> v1;
  v1.fun();
  v1.output();
   
}

當然:也可以用於函式

當你嘗試將模板類作為函式的形參時,需要指定模板引數。如果你想要靈活地傳遞不同型別的容器作為函式引數,可以使用函式模板。下面是一個示例:

#include <iostream>
#include <vector>
#include <list>

using namespace std;

// 使用函式模板作為引數
template <template <class, class> class Contain, class T>
void printContainer(const Contain<T, std::allocator<T>>& container)
{
    for(const auto& element : container)
    {
        cout << element << " ";
    }
    cout << endl;
}

int main()
{
    vector<int> vec = {1, 2, 3, 4, 5};
    list<double> lst = {1.1, 2.2, 3.3, 4.4, 5.5};

    // 使用函式模板列印不同型別的容器
    printContainer(vec);
    printContainer(lst);
    
    return 0;
}

當然你可以更靈活:這種方式使用了更通用的模板形式,即將 printContainer 函式改為接受任何型別的容器,而不是特定型別的模板容器。這種方式的優勢是更加靈活,可以接受任何型別的容器作為引數,而不僅僅侷限於特定的模板容器。因此,你可以將任何型別的容器傳遞給該函式,而不需要顯式地指定模板引數。

#include <iostream>
#include <vector>
#include <list>

using namespace std;

template <class T>
void printContainer(const T& container)
{
    for(const auto& element : container)
    {
        cout << element << " ";
    }
    cout << endl;
}

int main()
{
    vector<int> vec = {1, 2, 3, 4, 5};
    list<double> lst = {1.1, 2.2, 3.3, 4.4, 5.5};

    // 使用函式模板列印不同型別的容器
    printContainer(vec);
    printContainer(lst);
    
    return 0;
}

這種方式的缺點是,當你使用函式模板時,你失去了對特定容器型別的控制,因此在函式內部,你可能無法依賴於特定容器提供的特定成員函式或特性。因此,需要在編寫函式模板時更加小心謹慎,確保它適用於各種可能的容器型別。

這兩種方式的區別主要在於函式引數的型別和函式的靈活性:

  1. 指定模板引數的方式:

    • 第一種方式中,函式模板 printContainer 接受一個模板類 Contain 和一個元素型別 T 作為引數,允許你顯式地指定容器的模板引數型別。
    • 第二種方式中,函式模板 printContainer 接受一個模板引數 T,而函式引數型別是模板引數 T 的例項化,即任何型別的容器,因此不需要顯式指定容器的模板引數型別。
  2. 函式的靈活性:

    • 第一種方式具有更大的靈活性,因為你可以選擇性地指定容器的模板引數型別。這使得你可以更具體地控制函式接受的容器型別。
    • 第二種方式則更加通用和靈活,因為它可以接受任何型別的容器作為引數,無需顯式指定容器的模板引數型別。這種方式適用於更廣泛的情況,但在函式內部可能需要更多的型別檢查和處理。

因此,選擇哪種方式取決於你的需求和偏好。如果你需要對容器型別進行更精確的控制,或者希望提供更具體的介面,可以選擇第一種方式。如果你需要更通用的函式,能夠接受各種型別的容器,可以選擇第二種方式。

相關文章