一文速通C++非类型模板参数
模板参数
特化根据 <>
中的参数 即模板参数 进行特化,因此我们有必要介绍一下模板参数。
类型模板参数
类型模板参数,不过多介绍。
- 类型
template <class T>
- 允许定义默认类型
template<class T = int>
非类型模板参数
总的来说
在模板的非类型模板实参中,
允许定义默认值 template<int N = 0>
允许一下类型:
- 编译期常量整数值:
- 枚举, int,bool,char
- 浮点数double、float等不行!!
- 占位符(C++14起,见下文)
template<auto N, decltype(auto) M>
- 字符串字面量
const char* str
,char* str
(见下文) - std::nullptr_t —— 实际上就是空指针的类型
- 比较逆天,可读性极差,我不建议使用的
- 对象/函数/成员符号的指针
- 对象/函数的左值引用
整数值
编译期常量整数值:(浮点数不行!!)
- 枚举, int,bool,char
- 允许定义默认值
template<int N = 0>
enum Color {
RED = 100,
};
template <enum Color c, int N, bool M, char D = ' '>
void noTypeParam_int() {
std::cout << c << std::endl;
}
noTypeParam_int<RED, 1, false>(); // 100
字面量演化 char[]
字符串可作为非类型模板实参,支持部分特化(例如 const):
template<char* msg>
void noTypeParam_literalString() {
cout << msg << endl;
}
template<const char* msg>
void noTypeParam_literalConstString() {
cout << msg << endl;
}
C++17 允许编译期常量求值结果(无链接(static)的字符串字面量),作为非类型模板实参,所以可以 定义数组
在与实例化时相同的作用域内。
void fromCpp17() {
// 局部链接性 static
static char msg1[] = "hello1";
noTypeParam_literalString<msg1>();
static const char msg2[] = "hello2";
noTypeParam_literalConstString<msg2>();
}
但是目前仍然不支持传字面量给模板参数
noTypeParam_literalString<"hi">(); // ERROR
noTypeParam_literalConstString<"hi">(); // ERROR
C++11 之前,仅支持外部链接字符串。
extern const char hello[] = "Hello World!";
void beforeCpp11() {
noTypeParam_literalConstString<hello> msg;
}
C++11之后,17之前。支持内部链接性字符串。
const char hello11[] = "Hello World!";
void fromCpp11() {
noTypeParam_literalConstString<hello11> msg11;
}
占位符
基本规则
C++17起,可使用占位符类型作为非类型模板参数的类型 —— 从而可以用泛型代码处理不同类型的非类型模板参数。
占位符类型是指:auto
,decltype(auto)
-
支持部分特化
// 使用占位符 auto template<auto N> class S { }; // 支持部分特化 template<int N> class S<N> {}; S<42> s1; // N的类型是int,使用对应特化 S<'a'> s2; // N的类型是char
-
不支持原本就不能用作非类型模板参数的类型,例如 double
S<3.5> s3; // 非类型模板参数的类型不能是 double
-
支持 CTAD 类模板参数推导
// 3.支持 CTAD 类模板参数推导 template<typename T, auto N> class A { public: A(const std::array<T, N>&) {} A(T(&)[N]) {} }; // CTAD 推导得到 // - A的类型 <const char, 6> // - N的类型 std::size_t A a1{"hello"}; // - A的类型 <double, 10> // - N的类型 std::size_t std::array<double, 10> sa2; A a2{sa2};
-
占位符是可以修饰的(cv限定符,指针等等)
// 4.占位符 auto 是可以修饰的
// 确保参数类型是指针
template<auto* P> struct S;
// cv 限定符
template<const auto P> struct S;
// 使用可变参数模板,使用多个不同类型的模板参数来实例化模板
template<auto... VS> class HeteroValueList {};
template<auto V1, decltype(V1)... VS> class HomoValueList {};
auto
简化操作
可以更便捷的定义非类型模板参数中的编译期常量。
// 原本需要指定两个模板参数,分别指定类型和值
template<typename T, T v>
struct constant { static constexpr T value = v; };
auto a = constant<int, 42>{};
auto b = constant<char, 'x'>{};
// 使用 auto模板参数特性 可以简单的实现为
template<auto v>
struct constant { static constexpr auto value = v; };
auto a = constant<42>{};
auto b = constant<'x'>{};
using c = constant<true>;
// 原本需要指定两个模板参数,分别指定类型和值
template<typename T, T... Elements>
struct sequence {};
using indexes = sequence<int, 0, 3, 4>;
// 使用 auto模板参数特性 可以简单的实现为
template<auto... Elements>
struct sequence {};
using indexes = sequence <0, 3, 4>;
静态多态
可定义即可能是默认类型,还可能是其它类型的模板参数。
例如,改进 用折叠表达式输出任意数量参数
的方法
#include <iostream>
template<auto Sep = ' ', typename First, typename... Args>
void print(const First& first, const Args&... args) {
std::cout << first;
auto outWithSep = [] (const auto& arg) {
std::cout << Sep << arg;
};
(... , outWithSep(args));
std::cout << '\n';
}
// 打印出7.5 hello world
std::string s{"world"};
print(7.5, "hello", s); // 默认值
// 打印出7.5‐hello‐world
print<'‐'>(7.5, "hello", s); // char类型 '-'
// 打印出7.51hello1world
print<1>(7.5, "hello", s); // int类型 1
// 前面我们说过,字符串字面量也可以作为非类型模板参数
// 占位符auto也允许传递,被声明为无链接的字符串字面量作为分隔符
// 打印出7.5, hello, world
static char sep[] = ", ";
print<sep>(7.5, "hello", s); // 字符串 char[] 类型 ", "
实现变量模板
可以使用 auto作为模板参数来实现 变量模板 (variable templates
)。
template<typename T, auto N>
std::array<T, N> arr;
在每个编译单元中
-
所有对 arr<int, 10>的引用将指向同一个类型。
-
当然,
arr<long, 10>
和arr<int, 10u>
都将分别指向对应类型。
可以声明一个任意类型的常量变量模板:
// 可以声明一个任意类型的 constexpr变量
template<auto N>
constexpr auto val = N;
// 类型可以通过初始值推导出来
auto v1 = val<5>; // 类型为int, 值为 5
auto v2 = val<true>; // 类型为bool, 值为 true
auto v3 = val<'a'>; // 类型为char, 值为 'a'
std::is_same_v<decltype(val<5>), int> // false
std::is_same_v<decltype(val<5>), const int> // true
std::is_same_v<decltype(v1), int> // true 因为auto会退化
万能推导 decltype(auto)
decltype(auto)(C++14引入)是另一个占位类型,也可以作为模板参数。
推导规则:推导的结果将依赖于**表达式expressions
的值类型**,而不是 变量名
的类型
表达式值类型 | 推导类型 |
---|---|
纯右值 prvalue | type |
左值 lvalue | type& |
将亡值 xvalue | type&& |
对于表达式,它的值类型是有意义的,有时我们希望捕捉它:
-
定义一个和表达式返回值类型一样的变量
decltype(auto) p = func();
-
完美转发函数返回值
decltype(auto) func(int && val) { return std::move(val); } // int &&
-
作为模板参数
template<decltype(auto) N>
struct S {};
static const int c = 42;
static int v = 42;
int main() {
S<c> s1; // 类型推导为const int
S<(c)> s2; // 类型推导为const int&
S<(v)> s3; // 类型推导为 int&
}