有以下简单模拟实现tuple的代码:
#include <cassert>
struct nulltype {};
template <typename... Args>
class mytuple;
template <>
class mytuple<> {
public:
bool isNull = true;
};
template <typename T, typename... Args>
class mytuple<T, Args...> {
public:
T data;
mytuple<Args...> others;
bool isNull = false;
mytuple(T value, Args... args) : data(value), others(args...) {}
template <std::size_t N>
decltype(auto) get() {
if constexpr (N == 0) {
return data;
}
else {
assert(N < sizeof...(Args) + 1, "Index out of range");
return others.template get<N - 1>();
}
}
};
知识点1:
一般情况下, 在定义了类模板之后再进行空参数列表的模板特化是会编译错误的, 因为一般情况下模板特化的参数数量必须与定义的类模版参数数量一致, 编译器会做检查, 检查不通过就报错并且不会生成空模板参数的类模板实例. 而先声明, 再特化, 后定义, 编译器就不会事先做这个检查, 而是直接乖乖生成空模板参数的类模板实例, 这样接下来的程序中就可以正常调用到这个空模板参数的版本了.
注: 先声明后定义的情况下, 定义类模板时必须在类名后面加上参数, 已表明这是定义:
知识点2:
在C++中,当你在一个模板类的成员函数中使用另一个模板类的成员函数时,有时候需要使用 template 关键字来明确指出你正在引用的是一个模板成员。这通常发生在以下两种情况下:
(1)依赖名称
当你在一个模板类的成员函数中使用一个依赖于模板参数的名称时,编译器无法确定这个名称是一个类型、一个静态成员、还是一个模板。在这种情况下,你需要使用 template 关键字来明确告诉编译器,这是一个模板成员。
(2)嵌套模板
如果你在一个模板类的成员函数中引用了另一个模板类的模板成员函数,编译器需要明确指出这个成员函数是一个模板。这是因为 C++ 并不总是能够自动识别模板成员函数的模板性质。
比如:
知识点3:
关于类模板与decltype(auto)返回值自动推导, 它们本质是一样的, 都必须要求编译器能在编译阶段就知道类模版的确切类型/函数返回值的确切类型, 以便生成实例代码, 因此get函数在使用decltype(auto)作返回类型推导时, 必须要能知道返回类型究竟是if语句下的哪条路径指定的, 故而需要将get设为函数模版, 并且巧妙利用非模板参数:
template <std::size_t N>
decltype(auto) get() {
if constexpr (N == 0) {
return data;
}
else {
assert(N < sizeof...(Args) + 1);
return others.template get<N - 1>(); //必须明确告知编译器, get是一个函数模版
}
}
mytuple<int, char, double> m(10, 'c', 3.4);
std::cout << m.get<0>() << std::endl; // Output: 10
std::cout << m.get<1>() << std::endl; // Output: c
std::cout << m.get<2>() << std::endl; // Output: 3.4
搭配上述调用处的代码, 编译器就能根据编译期就有的常量N(0, 1, 2), 进行 if 判断, 判断出函数究竟该return data; 还是return others.template get();//此时编译器会先往下递推 然后生成出真正的函数模板实例.