c++ 模板模板参数("Template Template Parameters")
#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++ 中,模板参数不仅可以是类型(例如 int
、float
等),也可以是整型常量、指针、引用、甚至是类模板。这些参数在 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; }
这种方式的缺点是,当你使用函数模板时,你失去了对特定容器类型的控制,因此在函数内部,你可能无法依赖于特定容器提供的特定成员函数或特性。因此,需要在编写函数模板时更加小心谨慎,确保它适用于各种可能的容器类型。
这两种方式的区别主要在于函数参数的类型和函数的灵活性:
-
指定模板参数的方式:
- 第一种方式中,函数模板
printContainer
接受一个模板类Contain
和一个元素类型T
作为参数,允许你显式地指定容器的模板参数类型。 - 第二种方式中,函数模板
printContainer
接受一个模板参数T
,而函数参数类型是模板参数T
的实例化,即任何类型的容器,因此不需要显式指定容器的模板参数类型。
- 第一种方式中,函数模板
-
函数的灵活性:
- 第一种方式具有更大的灵活性,因为你可以选择性地指定容器的模板参数类型。这使得你可以更具体地控制函数接受的容器类型。
- 第二种方式则更加通用和灵活,因为它可以接受任何类型的容器作为参数,无需显式指定容器的模板参数类型。这种方式适用于更广泛的情况,但在函数内部可能需要更多的类型检查和处理。
因此,选择哪种方式取决于你的需求和偏好。如果你需要对容器类型进行更精确的控制,或者希望提供更具体的接口,可以选择第一种方式。如果你需要更通用的函数,能够接受各种类型的容器,可以选择第二种方式。