实际的工程中有一个情况需要用回调函数来处理具体的数据,这里需要处理非类型模板参数,在处理完成后,又想把其它几种情况都写一下。在这个回调函数中,需要处理一下不同情况下,模板的模板参数问题,什么是模板的模板参数?就是模板的参数是模板的情况,前面有过分析,不再详细说明,此次只是用一个实际的例子来说明一下如何使用。
这个实际的例子是把内部的参数指针通过回调函数引用传回给应用 ,由应用来处理相关操作。那么此处就有一个问题,在回调函数中,本身就是一个模板参数,该怎么处理?看它的基本版 ,也就是不使用模板的模板参数:
template <typename T, size_t N = 100> struct Data final {
......
};
std::optional<size_t> Add(std::function<void(Data<T> *&, size_t)> fn) {
......
}
这样的话在应用层直接写一个类似的函数绑定到std::function即可。但是这种方法灵活性较差,所以修改了几个版本:
#include
#include
using namespace std;
//常量定义,用于模板参数
constexpr size_t num = 100;
//三种不同的模板类定义,供做模板参数
template <typename T>
struct SData final {
std::array<T, 100> Buf = {0};
};
template <typename T, typename N>
class SSData {
//std::array Buf = {0};//此处N为非常数,关闭
};
template <typename T, size_t N = 100>
class SoData {
std::array<T, N> Buf = {0};
};
//三个匹配的调用函数
std::optional<size_t>
getData(SData<A> *&sd,size_t size = 100) {
std::cout << "this is test" << std::endl;
}
std::optional<size_t>
getData2(SSData<A, size_t> *&sd,size_t size = 100) {
std::cout << "this is test2" << std::endl;
}
// num can ignore
std::optional<size_t>
getData3(SoData<A, num> *&sd,size_t size = 100) {
std::cout << "this is test3" << std::endl;
}
//三个模板函数
template <typename T, template <typename N> typename Con>
using Fn = std::function<std::optional<size_t>(Con<T> *&, size_t)>;
template <typename T, typename P,
template <typename N, typename U> typename Con>
using Fn2 = std::function<std::optional<size_t>(Con<T, P> *&, size_t)>;
template <typename T, size_t P,
template <typename N, size_t U = 100> typename Con>
using Fn3 = std::function<std::optional<size_t>(Con<T, P> *&, size_t)>;
//模板参数类,形式上用
struct A {
......
};
int main() {
Fn<A, SData> fn = getData;
Fn2<A, size_t, SSData> fn2 = getData2;
Fn3<A, num, SoData> fn3 = getData3; num can ignore
SData<A> *smd =new SData<A>;
SSData<A, size_t> *ssmd =new SSData<A, size_t>;
SoData<A, num> *somd =new SoData<A, num>; num can ignore
fn(smd, 100);
fn2(ssmd, 100);
fn3(somd, 100);
return 0;
}
此次遇到的主要问题和解决方式:
1、名字写错的问题,这种低级错误很麻烦,主要是思维惯性,休息一下往往就找出来了。
2、由于使用工具,有时候儿会自动推出中括号,从而把模板参数(特别是定义变量时)用中括号包裹(应该是尖括号),导致问题。
3、如果在实际情况中遇到不好解决的模板问题,可以从实例化向回推,更容易理解并找到问题。
4、重视编译警告和错误提示,特别本次给Fn系列赋值时,错误提示很准确。
5、从基础出发,不要一开始就搞得特别大,如果已经搞得特别大,可以简化一下模型,成功后再回推。
最重要的是,模板的模板参数其实类似于竹笋,内层的嵌套依赖于外部的模板参数,外部的模板参数对内部的模板参数来说等同于实现,这个非常重要。换句话说,你是无法在最外层直接操作下一层的模板参数的。这点非常重要,切记。另外,在模板参数声明时,层间的参数定义是同等地位的,也就是说,下一层也无法使用上一层的模板参数,否则会报“Declaration of ‘T’ shadows template parameter”。上层的定义覆盖了下层的定义,这个学过C++的都知道原因。
说明一下,通过某种手段变相去操作内部的模板参数不是直接操作,这里的直接操作就是直接实例化。
有时间可以把它写成一个变参模板的实现,估计适应性会更好。
本来是想简化一下工程实践上的操作,同时可以使用decltype来处理回调函数的返回值,结果事以愿违,这个模板的匹配让工作反而变得复杂起来。不过,借此机会,把模板匹配有了一个更强的理解,也是一件幸事。
实践出真知,这句话真是非常有道理。