🌈 个人主页:Zfox_
🔥 系列专栏:C++从入门到精通

一:🔥 constexpr

🦋 顶层 const 和底层 const

  • 指针本身是一个对象,它可以指向另一个对象,因此指针及到本身是不是 const 和指向的对象是不是 const 的问题,C++用为了好区分,把本身是 const 修饰符叫做 const把指向的对象是 const 修饰叫做底层 const
  • 大多数对象被 const 修饰都叫顶层 const,指针被 const 修饰时,*左边的 const 叫底层 const,*右边的 const 叫做顶层 const。
  • const 修饰引用时,这个 const 是底层 const。
int i = 0;
int* const p1 = &i; // 顶层const
const int c1 = 42; // 顶层const
const int* p2 = &c1; // 底层const
const int& r = c1; // 底层const

🦋 constexpr 和常量表达式

  • 常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式,字面值、常量表达式初始化的 const 对象都是常量表达式,要注意变量初始化的 const 对象不是常量表达式。
  • constexpr(constant expression) 是 C++11 引入的一个关键字用于指定常量表达式。它允许在编译时计算表达式的值,从而提高运行时性能并增强类型安全性。
  • constexpr 可以修饰变量,constexpr 修饰的变量一定是常量表达式,且必须用常量表达式初始化,否则会报错。(看完下面就知道这里可以让编译器去放心的优化)(也不能间接修改了)
  • constexpr 可以修饰指针,constexpr 修饰的指针是顶层 const,也就是指针本身。(int* const ptr)
const int a = 1;
const int b = a + 1; // b是常量表达式
int c = 1;
int d = c; // d不是常量表达式
const int e = sizeof(c); // e不是常量表达式

int size() {
    int n = 16;
    return n;
}

是不是常量表达式的点在于会不会影响编译器的优化,看如下代码:

// i是常量表达式
// volatile const int i = 0;
const int i = 0; // 常量表达式
cout << i << endl;
int *ptr = (int*)&i;
*ptr += 1;
cout << i << endl;

int j = 8;
const int k = j; // 不是常量表达式
cout << k << endl;
ptr = const_cast<int*>(&k);
*ptr += 1;
cout << k << endl;

此时 i 的值仍然是 0,因为在编译期,i 就被替换成了 0
使用 volatile 就可以了,不优化,每次都从内存去取
而 k 不是常量表达式,就不会被编译器在编译器替换掉

所以不确定到底是不是常量表达式导致的编译器优化,c++ 就引入了 constexpr

在这里插入图片描述

🦋 constexpr 函数

  • constexpr 普通函数,要求函数声明的参数和返回值都是字面值类型(整型、浮点型、指针、引用等),函数返回类型不能是 void。要求函数体中,只包含一条 return 返回语句,不能定义局部变量,循环条件判断等控制流,并且返回值必须是常量表达式。
  • constexpr 构造函数,constexpr 不能修饰自定义类型,但是用 constexpr 修饰的构造函数后可以就可以。该类的所有成员变量必须字面类型 (literal类型),constexpr 构造函数必须在初始化列表初始化所有成员变量,构造对象的实参必须使用常量表达式,函数体必须为空,析构函数必须是默认的不做任何实际清理工作。
  • constexpr 成员函数,constexpr 成员函数自动成为 const 成员函数,这意味着它们不能修改对象的成员变量,其他要求跟普通函数一样,另外 constexpr 函数不能是虚函数。
  • constexpr 可以修饰模板函数,但由于模板中类型的不确定性,因此模板函数实际中的函数是否符合常量表达式函数的要求也是不确定的。C++11标准规定,如果 constexpr 修饰的模板函数实际化结果不满足常量表达式函数的要求,则 constexpr 自动忽略,即该函数就等同于一个普通函数。
constexpr int x = size();
constexpr int y = func(1);
constexpr int z = factorial(5);

cout << x << endl;
cout << y << endl;
cout << z << endl;

// 直接调用不会优化,必须要用常量表达式
// 编译器求值

🦋 constexpr 在 C++14 中的演进

C++14 最显著的改进是大幅放宽了对 constexpr 函数的限制,使其语法和功能更接近普通函数。

函数限制的全面放宽

  • 局部变量:允许声明和初始化局部变量(只要在 constexpr 上下文中使用)
  • 控制流语句:支持 if 条件分支、for/while 循环、switch 语句等
  • 多 return 语句:函数体不局限于单一 return 语句
  • 支持更复杂的返回类型:如 void(返回),自定义类、STL容器 (std::array)、其他符合 constexpr 要求的复杂类型

比如 strlen 这种函数也可以是 constexpr,编译期就计算完成了

constexpr size_t stringLength(const char* str) {
	size_t len = 0;
	while (str[len] != '\0')
		++len;
	return len;
}

constexpr size_t len = stringLength("Hello"); // 编译期计算:5
constexpr std::array<int, 5> createArray() {
    std::array<int, 5> arr = {1,2,3,4,5};
    for (size_t i = 0; i < arr.size(); ++i) {
        arr[i] = i * i;
    }
    return arr;
}

int main() {
    Point p1 = midpoint({ 1,1,1,1 }, { 2,2,2,2 });
    constexpr Point p2 = midpoint({ 1,1,1,1 }, { 2,2,2,2 });

    constexpr std::array<int, 5> a1 = createArray();
}
constexpr auto square = [n](int x) constexpr { return x * x * n; };

// 相对有限支持的constexpr
constexpr auto sorted = sort_example(); // {1,2,3,4,5}

正如C++专家所评价的 : “C++14 将 constexpr 从一个受限的语法标记转变为真正实用的编译期编程工具,开启了现代 C++元编程的新范式”,这些改过为后续 C++17 和 C++20 中更强大的 constexpr 功能奠定了基础,使编译期计算成为现代 C++ 不可或缺的核心特性。

🦋 constexpr 在 C++17 中的演进

C++17 对 constexpr 进行了重大扩展,使其能大幅提升,进一步模糊了编译时和运行时的界限

if constexpr - 编译期条件分支

constexpr if 是 C++17 引入的一种条件编译语它允许在编译时根据常量表达式的结果决定编译哪部分代码,未选择的分支代码不会编译成指令,直接丢弃。(这里我们稍微提一下,更多细节 C++17 章节细讲)

template <typename T>
auto get_value(T t) {
    if constexpr(std::is_pointer_v<T>) {
	    		return *t; 	// 仅当T为指针类型时实例
		} else {
			  return t;		// 非指针类型时实例化
		}
}

constexpr lambda 表达式

  • lambda 表达式可标记为 constexpr
  • 捕获必须是编译期常量
  • 函数体需满足 constexpr 函数要求
int main()
{
	// constexpr lambda示例
	constexpr int n = 10;
	int y = 0;
	constexpr auto square = [n](int x) constexpr { return x * x * n; };
	constexpr int result = square(5); // 编译期计算:250

	return 0;
}

🦋 constexpr 在 C++20 中的演进

C++20 标准对 constexpr 关键字进行了革命性的增强,将编译期计算能力提升到了前所未有的高度。这些改进不仅大幅扩展了 constexpr 的应用范围,还使其成为现代 C++元编程 和性能优化的核心工具。下面将从多个维度全面解析 C++20 中 constexpr 的关键演进及其深远影响。

动态内存分配的编译期支持

  • new / delete 支持 : 允许在 constexpr 上下文中使用动态内存分配
  • 编译期容器 : 使得 std::vector积 std::string 等容器的编译期实现成为可能
  • 内存生命周期 : 所有分配的内存在编译期必须被释放
constexpr int dynamic_memory_example() {
	int* p = new int{ 42 }; // 编译期分配
	int value = *p;
	delete p; 							// 必须显式释放

	return value;
}

编译期就拍完序了,vector 就不行,vector 编译期开辟的内存不能被释放,array 是静态数组就可以

constexpr auto sort_example() {
	std::array<int, 5> arr{ 5, 3, 4, 1, 2 };
	std::sort(arr.begin(), arr.end()); // 编译期排序
	return arr;
}

int main()
{
	constexpr array<int, 10> a1 = { 3,2,1,4,5 };
	vector<int> v2 = { 3,2,1,4,5 };
	sort(v2.begin(), v2.end());
	//sort(a1.begin(), a1.end());
	auto it1 = find(v2.begin(), v2.end(), 3);

	// 相对有限支持的constexpr
	constexpr auto sorted = sort_example(); // {1,2,3,4,5}

	constexpr auto it2 = find(a1.begin(), a1.end(), 4);
	// 编译时断言
	//static_assert(*it2 == 4, "编译期查找");


	return 0;
}

还有一些 支持编译期多态调用 的等等细节就不再赘述了

二:🔥 处理类型

🦋 auto

  • auto 是一个类型说明符,他让编译器替我们分析表达式的类型, auto x = y + z; 编译器自动根据y+z相加的结果来推导x的类型,在一些类型比较长的场景,如前面讲的迭代器遍历时非常有用。
  • 编译器推导 auto 类型时,有时候也会和初始值的类型不一样,编译器会适当的改变结果类型,使其更符合初始化规则。首先使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值,所以编译器推导 auto 为引用对象的类型,而不是引用其次一个带有 const 属性的值初始化 auto 对象推导时忽略掉顶层 const,保留底层 const。
  • auto 不能自动推导出引用类型,所以我们如果想将 auto 推导为引用类型,需要明确的指出:
auto& x = i;
  • auto 不能推导出顶层 const,如果想使用 auto 推导出顶层 const,需要明确的指出:
const auto x = ci;
  • 设置一个类型为 auto 引用时,初始值中的顶层 const 属性仍然保留,否则存在权限放大问题。

🦋 decltype

  • 如果我们希望用表达式推出变量的类型,但是不想用表达式的值初始化变量,那么这时可以使用 decltypedecltype(f()) x; 需要注意的是编译器并不会实际调用函数,而是用f的返回类型作为x的类型。

  • decltype 处理 const 和引用的方式和 auto 也有所不同,decltype(const变量表达式) x,x的类型推出类型为 const Tdecltype 会保留顶层 constdecltype(引用变量表达式) x,x的类型推出类型为 T &decltype 会保留引用;要注意这里跟 auto 是完全不同的。

  • decltype 还有一些特殊处理比较奇怪,decltype(*p) x; x的类型是 T&decltype 推导解引用表达式时,推出类型是引用;decltype((i)) x; x的类型是 T&decltype 推导解括号括起来的左值表达式时,推出类型是引用;

🦋 尾置返回类型

尾置返回类型是 C++11 引⼊的⼀种函数声明语法,它允许将函数的返回类型放在参数列表之后⽽不是函数名前。尾置返回类型的语法这⾥我们简单的做个了解即可,因为 C++14 引⽤了 auto 做返回类型时,返回类型⾃动推导,很多地⽅就不太需要尾置返回类型了。

基本语法

auto functionName(parameters) -> returnType {
	// 函数体
}

为什么需要尾置返回类型

  1. 提⾼代码可读性:特别是当返回类型很⻓或复杂时
  2. ⽀持 Lambda 表达式:Lambda 表达式的返回类型必须使⽤尾置语法
  3. 模板编程:在模板函数中,返回类型可能依赖于参数类型
// 1. 复杂返回类型
auto getComplexType() -> std::map<std::string, std::vector<int>> {
	// ...
}

// 2. 依赖参数类型的返回类型
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
	return t + u;
} 

// 3. lambda表达式
auto lambda = [](int x) -> double { return x * 1.5; };

🦋 typedef 和 using

  • C++98 中我们⼀般使⽤typedef重定义类型名,也很⽅便,但是 typedef 不⽀持带模板参数的类型重定义。C++11 中新增了 using 可以替代 typedef,using的别名语法覆盖了typedef的全部功能,不少场景还更清晰⼀些,⽐如函数指针的重定义,其次最⼤的变化是⽀持带模板参数重定义的语法。
  • using 类型别名 = 类型;
// typedef map<string, int> CountMap;
// typedef map<string, string> DictMap;
// typedef int DateType;
// typedef void (*Callback)(int);

// using 兼容typedef的⽤法
using CountMap = map<string, int>;
using DictMap = map<string, string>;
using STDateType = int;
using Callback = void (*)(int);
// using⽀持带模板参数的类型重定义
template<class Val>
using Map = map<string, Val>;

template<class Val>
using MapIter = typename map<string, Val>::iterator;

三:🔥 强类型枚举(enumclass)

C++11引⼊了强类型枚举(也称为枚举类 enumclass),解决了传统C++枚举(enum)的多个缺点,提供了更好的类型安全性和封装性。

传统C++枚举存在以下主要问题:

  1. 隐式转换为整型:枚举值会⾃动转换为整数,可能导致意外⾏为
  2. 污染外围作⽤域:枚举值会泄漏到包含它的作⽤域中
  3. ⽆法指定底层类型:不能明确控制枚举使⽤的存储⼤⼩
enum class EnumName [: UnderlyingType] {
	enumerator1,
	enumerator2,
	// ...
};

强类型枚举语法:

  • enum class 或 enum struct (两者等价)
  • 可选的底层类型( : UnderlyingType )
  • 枚举值必须通过枚举名作⽤域访问

四:🔥 static_assert

static_assert 是 C++11 引⼊的编译时断⾔机制,它允许开发者在编译期间检查条件是否满⾜,如果条件不满⾜,则会导致编译错误。 static_assert 是 C++ 元编程和模板编程中⾮常有⽤的⼯具,它可以帮助开发者在编译期捕获错误,提⾼代码的健壮性。

// 常量表达式:在编译时可求值的表达式,必须能转换为 bool 类型
// 错误消息:当断⾔失败时显⽰的字符串字⾯量( C++17 起可以省略)

static_assert(常量表达式, 错误消息);

常⻅使⽤场景

// 1、类型检查
template<typename T>
void process(T value) {
	static_assert(std::is_integral<T>::value, "T must be an integral type");
	// 函数实现...
} 

// 2、编译时常量验证
constexpr int buffer_size = 1024;
static_assert(buffer_size > 0, "Buffer size must be positive");
static_assert(buffer_size % 4 == 0, "Buffer size must be divisible by 4");'

// 3、平台或架构检查
static_assert(sizeof(void*) == 8, "This code requires 64-bit platform");

// 4、类型⼤⼩验证
static_assert(sizeof(int) == 4, "int must be 4 bytes");

与运⾏时 assert 的区别
在这里插入图片描述

五:🔥 std::tuple

std::tuple 是C++11引⼊的⼀个模板类,它允许将多个不同类型的值组合成⼀个单⼀的对象。类似于结构体,但不需要预先定义类型名称。tuple(元组)是⼀个固定⼤⼩的异构值集合,可以包含不同类型的元素。它是 std::pair 的泛化版本, pair 只能保存两个元素,⽽ tuple 可以保存任意数量的元素。
https://legacy.cplusplus.com/reference/tuple/tuple/?kw=tuple

创建Tuple

#include <tuple>

int main() 
{
	// 创建⼀个包含3个元素的tuple: int, double, string
	std::tuple<int, double, std::string> t1(10, 3.14, "hello");
	
	// 使⽤make_tuple⾃动推导类型
	auto t2 = std::make_tuple(20, 2.718, "world");
	
	// C++17起可以使⽤类模板参数推导
	std::tuple t3(30, 1.618, "cpp");  // ⾃动推导为tuple<int, double, const char*>
	
	return 0;
}

访问元素

#include <tuple>

int main() {
	// 创建⼀个包含3个元素的tuple: int, double, string
	std::tuple<int, double, std::string> t1(10, 3.14, "hello");
	
	// 通过索引访问
	std::cout << std::get<0>(t1) << std::endl; // 输出10
	std::cout << std::get<1>(t1) << std::endl; // 输出3.14
	std::cout << std::get<2>(t1) << std::endl<< std::endl; // 输出"hello"
	
	// 修改
	std::get<0>(t1) = 100; // 修改第⼀个元素
	
	// C++14起可以通过类型访问(类型必须唯⼀)
	std::cout << std::get<int>(t1) << std::endl; // 输出100
	std::cout << std::get<double>(t1) << std::endl; // 输出3.14
	
	return 0;
}

解包tuple

int x;double y;
std::string z;

// 使⽤std::tie解包 也可以解包 pair
std::tie(x, y, z) = t1;

// C++17结构化绑定
auto [a, b, c] = t1;

六:🔥 模板元编程

现代 C++ 的⼀个进化⽅向就是在编译时做更多的⼯作,模板元编程(TemplateMetaprogramming, TMP) 是 C++ 中⼀种利⽤模板机制在编译期进⾏计算和代码⽣成的⾼级技术。它通过模板特化、递归实例化和类型操作,在编译时完成传统运⾏时才能处理的任务,从⽽实现零运⾏时开销的优化。下⾯我将从核⼼概念、关键技术、现代发展等⽅⾯全⾯讲解 C++ 模板元编程。

模板元编程最早由 ErwinUnruh 在 1994 年发现,他展⽰了如何让编译器在错误信息中输出素数序列,随后被 ToddVeldhuizen 和 DavidVandevoorde 等⼈系统化,ToddVeldhuizen 证明了 C++ 模板具有图灵完备性,理论上能执⾏任何计算任务,它遵循函数式编程范式,模板参数作为不可变数据参与编译期计算

🦋 模板元编程的核⼼概念

模板元编程的本质是将计算从运⾏时转移到编译期,利⽤编译器作为"计算引擎"⽣成⾼效代码。其核⼼思想包括:

  1. 编译期计算:所有运算在编译阶段完成,结果直接嵌⼊最终程序
  2. 类型操作:通过模板参数推导和类型萃取(TypeTraits)操作类型
  3. 递归模板实例化:通过递归展开实现循环和条件逻辑
  4. 零运⾏时开销:结果在编译期确定,不增加程序运⾏负担

🦋 模板元编程基础语法

🎀 基本模板结构
模板元编程主要使⽤类模板(⽽⾮函数模板),因为类模板可以包含类型成员和静态成员,再利⽤模板特化和递归实现。

template <typename T>
struct MyTemplate {
	using type = T; // 类型成员
	static const int value = 42; // 静态成员
};

🎀 编译期值计算

最简单的模板元编程是编译期计算阶乘

template <unsigned int N>
struct Factorial {
	static const unsigned int value = N * Factorial<N - 1>::value;
};

// 终⽌条件特化
template <>
struct Factorial<0> {
	static const unsigned int value = 1;
};

int main()
{
	constexpr unsigned int fact5 = Factorial<5>::value; // 编译时计算出120
	
	return 0;
}

🎀 编译期类型计算

编译时获取或修改类型信息的操作

#include <iostream>

using namespace std;

namespace bit {

// 主模板
template <typename T> struct is_pointer {
    static constexpr bool value = false;
};

// 针对指针类型的偏特化
template <typename T> struct is_pointer<T*> {
    static constexpr bool value = true;
};

// 主模板,默认情况类型不同
template <typename T, typename U> struct is_same {
    static constexpr bool value = false;
};

// 特化版本,当两个类型相同时
template <typename T> struct is_same<T, T> {
    static constexpr bool value = true;
};

// 移除 const
// 主模板,默认情况下不改变类型
template <typename T> struct remove_const {
    using type = T;
};

// 针对 const T 的特化版本,移除 const
template <typename T> struct remove_const<const T> {
    using type = T;
};

// 移除 指针
template <typename T> struct remove_pointer {
    using type = T;
};

template <typename T> struct remove_pointer<T*> {
    using type = T;
};

template <typename T> struct remove_pointer<T* const> {
    using type = T;
};

void func() {
    static_assert(is_pointer<int*>::value, "int* is a pointer");
    // static_assert(bit::is_pointer<int>::value, "int is not a pointer");
    static_assert(is_same<int, int>::value, "int and int should be thesame");
    // static_assert(is_same<int, float>::value, "int and float should be different");
    static_assert(is_same<remove_pointer<int*>::type, int>::value, "int and int should be the same");
    static_assert(is_same<remove_const<const int>::type, int>::value, "int and int should be the same");
}

} // namespace bit

int main() 
{
    bit::func();
    return 0;
}

🎀 类型萃取 (type_traits)

  • 类型萃取是 C++ 模板元编程中的核⼼技术,它允许在编译时检查和修改类型特性。C++11 版本开始标准库在 <type_traits> 头⽂件中提供了⼤量类型萃取⼯具。类型萃取是通过模板特化技术实现的编译期类型操作,主要⽤途包括:检查类型特性、修改/转换类型、根据类型特性进⾏编译期分⽀。
  • https://en.cppreference.com/w/cpp/header/type_traits.html
  • 标准库常⻅的类型萃取
#include <type_traits>

// 1、基础类型检查
std::is_void<void>::value; // true
std::is_integral<int>::value; // true
std::is_floating_point<float>::value; // true
std::is_pointer<int*>::value; // true
std::is_reference<int&>::value; // true
std::is_const<const int>::value; // true

// 2、复合类型检查
std::is_function<void()>::value; // true
std::is_member_object_pointer<int (Foo::*)>::value; // true
std::is_compound<std::string>::value; // true (⾮基础类型)

// 3、类型关系检查
std::is_same<int, int32_t>::value; // 取决于平台
std::is_base_of<Base, Derived>::value;
std::is_convertible<From, To>::value;

// 4、类型修改
std::add_const<int>::type; // const int
std::add_pointer<int>::type; // int*
std::add_lvalue_reference<int>::type; // int&
std::remove_const<const int>::type; // int
std::remove_pointer<int*>::type; // int
std::remove_reference<int&>::type; // int

// 4、条件类型选择
std::conditional<true, int, float>::type; // int
std::conditional<false, int, float>::type; // float

// 5、类型推导
// 函数的返回结果类型
std::result_of<F(Args...)>::type; // C++17以后被废弃
std::invoke_result<F, Args...>::type; // C++17以后使⽤这个

template<class F, class... Args>
using invoke_result_t = typename invoke_result<F, Args...>::type;
  • C++17为类型萃取添加了 _v 和 _t 后缀的便利变量模板和类型别名
// C++11⽅式
std::is_integral<int>::value;
std::remove_const<const int>::type;

// C++14、C++17 更简洁的⽅式
std::is_integral_v<int>;
std::remove_const_t<const int>;

// C++17 引⼊的辅助变量模板
template<typename T>
inline constexpr bool is_integral_v = is_integral<T>::value;

// C++14 引⼊的辅助别名模板
template<typename T>
using remove_const_t = typename remove_const<T>::type;
  • 类型萃取库的⼀些使⽤样例展⽰
#include <iostream>

// using namespace std;

template <typename T> void process(T value) {
    if constexpr (std::is_pointer_v<T>) {
        // 指针类型的处理
        std::cout << "Processing pointer: " << *value << std::endl;
    } else if constexpr (std::is_integral_v<T>) {
        // 整数类型的处理
        std::cout << "Processing integer: " << value * 2 << std::endl;
    } else if constexpr (std::is_floating_point_v<T>) {
        // 浮点类型的处理
        std::cout << "Processing float: " << value / 2.0 << std::endl;
    } else {
        // 默认处理
        std::cout << "Processing unknown type" << std::endl;
    }
}

#if defined(_WIN32)
#include <winsock2.h>
using socket_t = SOCKET;
#else
using socket_t = int;
#endif

template <typename T> void close_handle(T handle) {
    if constexpr (std::is_same_v<T, SOCKET>) {
        closesocket(handle);
    } else {
        close(handle);
    }
}

int main() 
{
    // 使⽤
    int i = 42;
    process(i);       // Processing integer: 84
    process(&i);      // Processing pointer: 42
    process(3.14);    // Processing float: 1.57
    process("hello"); // Processing unknown type

    return 0;
}
  • 类型萃取在STL库中的⼀些使⽤剖析
// 类型萃取在stl中⼀些原理分析及应⽤
#pragma once
#include <assert.h>

namespace bit {

/*struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};*/

template <class Iterator> struct iterator_traits {
    typedef typename Iterator::iterator_category iterator_category;
    typedef typename Iterator::value_type value_type;
    typedef typename Iterator::difference_type difference_type;
    typedef typename Iterator::pointer pointer;
    typedef typename Iterator::reference reference;
};

template <class T> struct iterator_traits<T*> {
    typedef random_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
};

template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag) {
    typename iterator_traits<InputIterator>::difference_type n = 0;
    while (first != last) {
        ++first;
        ++n;
    }
    return n;
}

template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last,
           random_access_iterator_tag) {
    return last - first;
}

template <class InputIterator>
inline typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last) {
    typedef typename iterator_traits<InputIterator>::iterator_category category;
    return __distance(first, last, category());
}

template <class T> class vector {
public:
    typedef T* iterator;
    // 类模板的成员函数,也可以是⼀个函数模板
    template <class InputIterator>
    vector(InputIterator first, InputIterator last) {
        // 为了提⾼效率可以提前resize
        // 这⾥InputIterator是模板参数,随机迭代器⽀持减计算个数
        // 单向和双向迭代器只是迭代计数计算个数
        // resize(last - first);
        resize(bit::distance(first, last));
        while (first != last) {
            // push_back(*first);
            ++first;
        }
    }
    void resize(size_t n) {}

private:
    iterator _start = nullptr;
    iterator _finish = nullptr;
    iterator _end_of_storage = nullptr;
};

} // namespace bit

int main() 
{
    string s("hello world");
    bit::vector<char> v1(s.begin(), s.end());
    std::list<int> lt(10000, 1);
    bit::vector<int> v2(lt.begin(), lt.end());

    return 0;
}

🎀 SFINAE

  • SFINAE 是 SubstitutionFailureIsNotAnError ⾸字⺟缩写,意思是"替换失败不是错误",在模板参数推导或替换时,如果某个候选模板导致编译错误(如类型不匹配、⽆效表达式等),编译器不会直接报错,⽽是跳过该候选,尝试其他可⾏的版本。如果最后都没匹配到合适的版本,再进⾏报错。
  • SFINAE 的语法相对复杂难理解,在 C++20 以后,考虑使⽤概念 (Concepts) 替代绝⼤部分的 SFINAE,所以 SFINAE 我们了解⼀下,后续我们重点学习 C++20 的概念。
  • SFINAE 经典应⽤场景函数重载

编译器去检查支不支持某些运算符操作,函数调用

// 版本1:仅适⽤于可递增的类型(如 int)
template<typename T>
auto foo(T x) -> decltype(++x, void()) {
	std::cout << "foo(T): " << x << " (can be incremented)\n";
} 

// C++17 使⽤void_t优化上⾯的写法
//template<typename T>
//auto foo(T x) -> std::void_t<decltype(++x)> {
// std::cout << "foo(T): " << x << " (can be incremented)\n";
//}

// 版本2:回退版本
void foo(...) {
	std::cout << "foo(...): fallback (cannot increment)\n";
} 

int main() 
{
	foo(42); // 调⽤版本1(int ⽀持 ++x)
	foo(std::string("1111")); // 调⽤版本2(string 不⽀持 ++x)
}
#include <type_traits>
#include <iostream>

// 对于整数类型启⽤此重载
template<typename T>
typename std::enable_if_t<std::is_integral_v<T>, T>
add_one(T t) {
	return t + 1;
} 

// 对于浮点类型启⽤此重载
template<typename T>
typename std::enable_if_t<std::is_floating_point_v<T>, T>
add_one(T t) {
	return t + 2.0;
} 

// 模板参数的检查
template<typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
void process_integer(T value) {
	// 只接受整数类型
} 

int main() 
{
	std::cout << add_one(5) << "\n"; // 调⽤整数版本,输出6
	std::cout << add_one(3.14) << "\n"; // 调⽤浮点版本,输出4.14
	// add_one("hello"); // 编译错误,没有匹配的重载
	process_integer(1);
	// process_integer(1.1); // 编译错误,没有匹配的重载
}

七:🔥 现代 C++ 中对模板元编程特性的增强和优化

🦋 constexpr 函数(C++11起)

C++11/14/17/20 逐步增强了 constexpr 能⼒,许多模板元编程任务可以⽤constexpr 函数替代,允许函数和变量在编译期求值,替代部分传统模板元编程的递归实例化,constexpr 简化了很多相对复杂的模板元编程实现。

constexpr int factorial(int n) {
	return n <= 1 ? 1 : n * factorial(n - 1);
} 

constexpr int x = factorial(5); // 120

🦋 变量模板(C++14)

变量模板直接定义编译期常量值,有了变量模板类型萃取的⼀些取值特性就可以简化⼀些,如 is_integral_v 等

template<typename T>
constexpr T pi = T(3.1415926535897932385);

template<class T>
constexpr bool is_integral_v = is_integral<T>::value;

int main() 
{
	float f = pi<float>; // 单精度π
	double x = pi<double>; // 双精度π
	
	// 使⽤不同精度的π
	std::cout.precision(6);
	std::cout << "float π: " << f << std::endl;
	std::cout.precision(10);
	std::cout << "double π: " << x << std::endl;
	
	return 0;
}

🦋 ifconstexpr(C++17)

在编译期根据条件选择代码路径,避免⽣成⽆效代码分⽀,简化 SFINAE 和模板特化的复杂逻辑,提升可读性。

template <typename T>
auto process(T value) {
	if constexpr (std::is_integral_v<T>) {
		return value * 2;
	} else if constexpr (std::is_floating_point_v<T>) {
		return value / 2;
	} else {
		return value;
	}
}

🦋 折叠表达式(C++17)

简化可变参数模板的参数包展开操作,具体细节 C++17 章节中的讲解

template<typename... Args>
void print(Args&&... args) {
	(std::cout << ... << args) << '\n';
	// 等价于 (((std::cout << arg1) << arg2) << ...) << argN;
}

🦋 概念(C++20)

概念 (concept) 是 C++20 引⼊的模板参数约束机制,取代 SFINAE 的复杂约束语法。具体细节 C++20 章节中细讲

// 定义⼀个要求T是整形的概念
template< class T >
concept Integral = std::is_integral_v<T>;

// 1、模板参数后直接使⽤
template<Integral T>
void f1(T x)
{
	std::cout << "有 concepts 约束" << std::endl;
}

🦋 模块(C++20)

  • C++20引⼊的模块(Modules)是C++语⾔的⼀项重⼤⾰新,旨在解决传统头⽂件包含机制(#include)的诸多问题。其中⼀个问题就是,每次包含头⽂件时,编译器都需要重新解析其内容,导致编译时间⼤幅增加。模块引⼊以后可以⼤⼤缩短编译时间,具体特性,我们后⾯C++20再细讲。
  • C++20Modules代码在AlibabaHologres主线上已稳定运⾏⼀年半以上,并减少了42%的编译时间。https://xie.infoq.cn/article/5c81d38dcb3949dc0ebb58fa1
  • 模板元编程的⼀个重⼤问题就是让项⽬的编译时间变⻓,所以模块的引⼊可以很好的缓解这个问题

🦋 模板元编程优缺点分析

优点:

  1. 零运⾏时开销:所有计算在编译期完成
  2. 类型安全:编译期类型检查
  3. ⾼度抽象:可构建灵活通⽤的库

缺点:

  1. 编译时间⻓:复杂的模板实例化会增加编译时间
  2. 学习成本增加:很多模板元编程的写法晦涩难懂,⼤⼤增加学习成本
  3. 错误信息晦涩:模板错误通常难以理解
  4. 调试困难:难以调试编译期计算

十:🔥 共勉

😋 以上就是我对 C++11】扩展学习 的理解, 觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~ 😉
在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐