简介
模板元编程(template metaprogramming)是一种利用 C++ 模板机制在编译期生成或修改代码的技术,它可以用来实现一些运行期无法完成的任务,或者提高运行期的效率。模板元编程是一种典型的元编程(metaprogramming)方式,即用代码来操作代码。
// 定义一个概念 IsOdd,表示一个整数是否是奇数
template <typename T>
concept IsOdd = std::integral<T> && (T % 2 != 0);
// 定义一个模板函数,用于计算两个数的和
template <typename T>
T add(T a, T b) {
return a + b;
}
// 定义一个模板函数的特化,用于计算两个奇数的和,返回结果加一
template <IsOdd T>
T add(T a, T b) {
return a + b + 1;
}
int main() {
std::cout << add(2, 3) << std::endl; // 输出 5,调用普通的模板函数
std::cout << add(3, 5) << std::endl; // 输出 9,调用特化的模板函数
}
我们使用了概念 IsOdd 来约束模板参数 T 必须是一个奇数,然后我们对模板函数 add 进行了特化,使得当两个参数都是奇数时,返回结果加一。这样,我们就实现了一种编译期的分支逻辑,根据不同的参数类型选择不同的函数实现。这就是概念和模板元编程的结合。
模板元编程是一种利用 C++ 模板机制在编译期生成或修改代码的技术,它可以用来实现一些运行期无法完成的任务,或者提高运行期的效率。
模板元编程的优点有:
- 代码量变小。模板元编程可达成真正的泛用代码,促使代码缩小并较好维护。
- 类型系统的补充。模板元编程可以用来约束模板参数的要求,提高泛型编程的表达能力和安全性。模板元编程可以用来实现一些编译期的类型检查、类型转换、类型操作等功能。
- 编译期计算。模板元编程可以用来实现一些编译期的算法,比如递归、分支、循环等。这样可以避免运行期的开销,或者实现一些运行期无法做到的事情,同时因为编译期计算,所以在一定程度上能加快运行速度。
缺点:复杂
概念(C++20起)
概念(concept)是 C++20 引入的一种新的语言特性,它可以用来约束模板参数的要求,提高泛型编程的表达能力和安全性。概念本身并不是元编程的一部分,但是它可以和模板元编程结合使用,实现更强大的功能。
核心概念
先看第一个核心的概念:
std::same_as
std::same_as - cppreference.com
文档说明:
T和U都是模板参数
概念 same_as<T, U>(
T和U都是模板参数)
当且仅当 T
与 U
代表同一类型才得到满足,返回的结果是一个bool类型,如果类型一致为true,否则为false
std::same_as<T, U> 蕴含 std::same_as<U, T>,反之亦然。
直观理解:传入两个模板参数,只有T和U一样的特化成同一类型才能满足条件,这里的返回结果是一个概念。
文档示例:
#include <concepts>
#include <iostream>
template<typename T, typename ... U>
concept IsAnyOf = (std::same_as<T, U> || ...);
template<typename T>
concept IsPrintable = std::integral<T> || std::floating_point<T> ||
IsAnyOf<std::remove_cvref_t<std::remove_pointer_t<std::decay_t<T>>>, char, wchar_t>;
void println(IsPrintable auto const ... arguments)
{
(std::wcout << ... << arguments) << '\n';
}
int main()
{
println("例: ", 3.14, " : ", 42, " : [", 'a', L'-', L"Z]");
}
template<typename T, typename … U> 这里的省略号和 U 是用来表示可变参数模板的。可变参数模板是一种可以接受任意个数和类型的模板参数的模板,它可以用于类模板或函数模板。
在这个例子中,T 是一个普通的模板参数,表示一个固定的类型,而 U 是一个类型模板形参包,表示一个可变的类型序列。U 前面的省略号是用来声明形参包的,而 U 后面的省略号是用来展开形参包的。
concept IsAnyOf = (std::same_as<T, U> || ...); IsAnyOf 这个概念的含义是,T 必须和 U 中的任意一个类型相同。std::same_as<T, U> 是一个内置的概念,它表示 T 和 U 是否是同一种类型。|| 是逻辑或运算符,它表示只要有一个条件为真,就返回真。… 是用来声明和展开参数包的,它表示 U 可以是任意个数和类型的参数。
concept IsPrintable = std::integral<T> || std::floating_point<T> || IsAnyOf<std::remove_cvref_t<std::remove_pointer_t<std::decay_t<T>>>, char, wchar_t>;这里面出现了好几个概念的语法,
首先我们看
std::integral
概念 integral<T> 当且仅当 T
为整数类型才得到满足。
std::floating_point
概念 floating_point<T> 当且仅当 T
为浮点类型时得到满足。
std::decay_t (C++14)
std::decay 获取类型,use std::decay_t = std::decay<T>::type;
decay<T>::type获取T的类型,所以 decay_t 就是一个获取类型的泛型(C++14起)
std::remove_pointer_t (C++14)
using remove_pointer_t = typename remove_pointer<T>::type; 提供成员 typedef type
,其为 T
所指向的类型,或若 T
不是指针,则 type
与 T
相同,直接了当说,如果T是指针类型,那么获取解引用的对象的类型,比如 remove_pointer_t<int*> 就是值就是 int 类型 。
std::remove_cvref_t (C++20)
using remove_cvref_t = typename remove_cvref<T>::type; 直接了当,如果类型是引用,直接获取解引用的对象的类型。 比如 std::remove_cvref_t<int&> 的值就是 int,左值引用和右值引用都是一样的结果。
concept IsPrintable = std::integral<T> || std::floating_point<T> ||
IsAnyOf<std::remove_cvref_t<std::remove_pointer_t<std::decay_t<T>>>, char, wchar_t>; 这里是定义一个名为 IsPrintable 的概念的语法。IsPrintable 这个概念的含义是,T 必须是一个整数类型、浮点数类型、字符类型或宽字符类型,或者是这些类型的指针、引用或者去除修饰符后的类型。这个概念可以用来限制只有可打印的类型才能作为模板参数。
void println(IsPrintable auto const ... arguments) 这里面的 auto const ... arguments 是用来表示可变参数模板的一种写法。这个函数可以接受任意个数和类型的参数,只要这些参数都是常量的。auto 是用来自动推断参数的类型的,const 是用来限定参数不可修改的,... 是用来声明和展开参数包的。
从官方文档这个示例我们可以很清楚的明白 same_as 的作用了,就是传入的类型参数参数一致则为ture,否则为false。
更多具体的概念请看文档,内容比较多,不重复介绍了