模板是C++中很重要的一个特性,利用模板可以编写出类型无关的通用代码,极大的减少了代码量,提升工作效率。C++中包含类模板、函数模板,对于需要特殊处理的类型,可以通过特化的方式来实现特定类型的特殊操作。
最近工作中,需要处理CONT<TYPE>这种复合类型和T这种自定义类型的模板特化,因为CONT类型有五种左右需要特殊处理,其余的可以用默认处理函数,TYPE的具体类型有上千种,但是TYPE类型不涉及不同的操作。
方案一:采用两个模板参数
template <typename TYPE, template<typename> typename CONT> void func(const CONT<TYPE> &v) {} //或 template <typename T> void func(const T &v) {}
问题:由于C++不支持模板函数偏特化,实际特化的时候会退化的第二个的情形,即需要手动编写几千个函数来实现特化,这种方法不可接受。
方案二:采用偏特化类模板的第二个模板参数
template<typename TYPE, template<typename> typename CONT> struct my_class { my_class() = delete; static void func(const CONT<TYPE> &v) {} }; //将CONT偏特化为vector template <typename TYPE> struct my_class<TYPE, vector> { my_class() = delete; static void func(const vector<TYPE> &v) {} };
问题:首先,这种形式的偏特化可以实现复合类型的需求,但是对于用户自定义类型无法兼容
方案三:利用SFINAE控制模板函数实例化实现“偏特化” (示例代码部分使用C++17语法)
//“偏特化”vector容器类型的模板函数 template <typename TYPE, template<typename> typename CONT> void func(const CONT<TYPE> &v, std::enable_if_t<std::is_fundamental<TYPE>::value && std::is_same_v<vector<int>, CONT<int>>> * = nullptr) {} //如果使用C++11版本的gcc时,偏特化vector等标准容器类型的模板函数需要使用如下形式 template <typename T, template<class, class...> class C, class... Args> void var_func(const C<T, Args...>& v) { std::cout << "var_func type" << std::endl; } //“偏特化”用户自定义非容器类型的函数 template <typename TYPE, template<typename> typename CONT> void func(const TYPE &v, std::enable_if_t<!std::is_fundamental<TYPE>::value> * = nullptr) {} //默认的复合类型模板函数 template <typename TYPE, template<typename> typename CONT> void func(const CONT<TYPE> &v, std::enable_if_t<std::is_fundamental<TYPE>::value && !std::is_same_v<vector<int>, CONT<int>> && !std::is_same_v<deque<int>, CONT<int>> && !std::is_same_v<list<int>, CONT<int>>> * = nullptr) {} //对于函数体内的auto类型的变量(由CONT和TYPE组合成的复合类型),可以通过以下手段判断其类型 using T = std::decay_t<decltype(variable)>; if constexpr (std::is_same_v<vector<type>, T>) func<type, vector>(variable); else if constexpr (std::is_same_v<my_type, T>) func<my_type, deque>(variable); ....
结论:最后采用了这种方案,结合了std::enable_if_t, std::is_same_v, std::is_base_of 等语法来实现模板函数的条件来实现了函数模板的“偏特化”
Tips:
- 函数模板的定义和声明必须都放到头文件中,因为编译器在编译阶段需要依据头文件中模板函数的定义来实现模板的实例化
- 不建议将函数模板编译成静(动)态库,然后源文件只包含模板函数声明的头文件,这样在链接的时候提示undefined 的类似错误,因为函数库只能导出实例化的函数,对于没有实例化模板函数,就会出现undefined的错误(除非在函数库中,将所有可能的实例化类型都实例化一遍)。建议,对模板的使用通过头文件的方式。
- 类模板如果有默认模板参数,那么只能在声明或者定义其中任何一个位置设置默认模板参数,不能两个位置都设置。