当前位置: 首页 > article >正文

一文速通C++非类型模板参数

模板参数

特化根据 <>中的参数模板参数 进行特化,因此我们有必要介绍一下模板参数

类型模板参数

类型模板参数,不过多介绍。

  • 类型 template <class T>
  • 允许定义默认类型 template<class T = int>

非类型模板参数

总的来说

在模板的非类型模板实参中,

允许定义默认值 template<int N = 0>

允许一下类型:

  1. 编译期常量整数值
    • 枚举, int,bool,char
    • 浮点数double、float等不行!!
  2. 占位符(C++14起,见下文) template<auto N, decltype(auto) M>
  3. 字符串字面量 const char* str, char* str(见下文)
  4. std::nullptr_t —— 实际上就是空指针的类型
  5. 比较逆天,可读性极差,我不建议使用的
    • 对象/函数/成员符号的指针
    • 对象/函数的左值引用

整数值

编译期常量整数值:(浮点数不行!!)

  • 枚举, 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起,可使用占位符类型作为非类型模板参数的类型 —— 从而可以用泛型代码处理不同类型的非类型模板参数。

占位符类型是指:autodecltype(auto)

  1. 支持部分特化

    // 使用占位符 auto
    template<auto N> class S { };
    // 支持部分特化
    template<int N>  class S<N> {};
    S<42> s1;	// N的类型是int,使用对应特化
    S<'a'> s2;	// N的类型是char
    
  2. 不支持原本就不能用作非类型模板参数的类型,例如 double

    S<3.5> s3;	// 非类型模板参数的类型不能是 double
    
  3. 支持 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};
    
  4. 占位符是可以修饰的(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的值类型**,而不是 变量名的类型

表达式值类型推导类型
纯右值 prvaluetype
左值 lvaluetype&
将亡值 xvaluetype&&

对于表达式,它的值类型是有意义的,有时我们希望捕捉它:

  • 定义一个和表达式返回值类型一样的变量

    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&
}

http://www.kler.cn/a/567695.html

相关文章:

  • Windows安装sql server2017
  • Arduino控制舵机
  • 前端Npm面试题及参考答案
  • SQL 中的 EXISTS 子句:探究其用途与应用
  • Yolo11实战:基于YOLOv11的半自动化数据标注技术实践
  • SCIKIT-LEARN 决策树实现csv文档简单的推论预测
  • C++:string类(简单介绍)
  • DaoCloud 亮相 2025 GDC丨开源赋能 AI 更多可能
  • 翻译: 深入分析LLMs like ChatGPT 一
  • MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享
  • 【STM32(HAL库) RTC】(实时时钟)配置
  • 【力扣】2619. 数组原型对象的最后一个元素——认识原型与原型链
  • java23种设计模式-观察者模式
  • OpenHarmony应用间跳转
  • 在docker中运行R容器,并在Windows下的vscode中使用该R
  • 卷积神经网络(Convolutional Neural Network,CNN)详细解释(带示例)
  • nexus如何上传自己的依赖包
  • 工程化与框架系列(12)--响应式框架原理
  • 16.2 LangChain 表达式语言设计哲学:重新定义大模型应用开发范式
  • 4.3MISC流量分析练习-wireshark-https