C++ 模板元编程(1)-概念 concept

简介

模板元编程(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++ 模板机制在编译期生成或修改代码的技术,它可以用来实现一些运行期无法完成的任务,或者提高运行期的效率。

模板元编程的优点有:

  1. 代码量变小。模板元编程可达成真正的泛用代码,促使代码缩小并较好维护。
  2. 类型系统的补充。模板元编程可以用来约束模板参数的要求,提高泛型编程的表达能力和安全性。模板元编程可以用来实现一些编译期的类型检查、类型转换、类型操作等功能。
  3. 编译期计算。模板元编程可以用来实现一些编译期的算法,比如递归、分支、循环等。这样可以避免运行期的开销,或者实现一些运行期无法做到的事情,同时因为编译期计算,所以在一定程度上能加快运行速度。

缺点:复杂


概念(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。


更多具体的概念请看文档,内容比较多,不重复介绍了

标准库标头 <concepts> - cppreference.com

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值