文章目录
-
1 关键字
-
2 语义语法
-
-
2.1 size_t字面量
-
2.2 lambda表达式的空圆括号
-
2.3 标识符支持Unicode标准附录31
-
2.4 允许属性重复
-
2.5 向下类型转换为bool类型
-
2.6 规范行尾反斜杆
-
2.7 取消混合的字符串字面量连接语法
-
2.8 非静态数据成员的地址
-
2.9 移除垃圾回收的支持
-
2.10 简化隐式移动语义
-
2.11 this推导
-
2.12 更改lambda中的作用域
-
2.13 多维下标运算符
-
2.14 constexpr函数中常量语境下的变量
-
2.15 字符集与字符编码
-
2.16 一致的字符字面量编码
-
2.17 初始化声明新增支持别名声明
-
2.18 lambda表达式新增属性支持
-
2.19 复合语句末尾的标签
-
2.20 重写==和!=运算符
-
2.21 移除无法编码的宽字符字面量和多字符宽字符字面量
-
2.22 新的八进制、十六进制和universal-character-name的转义序列
-
2.23 新增unicode编码的字符序列的转义序列
-
2.24 常量表达式中使用未知的指针和引用
-
2.25 static operator()
-
2.26 新增浮点数类型别名
-
2.27 从继承的构造函数中推导模板参数
-
2.28 支持UTF8作为可移植的源文件编码
-
2.29 显式生命周期管理
-
2.30 static operator[]
-
2.31 允许constexpr函数中定义static constexpr变量
-
2.32 consteval向上传导
-
2.33 更有意义的export
-
2.34 修复基于范围的for循环的问题
-
-
3 预处理指令
-
4 属性
-
5 标准库
C++23新特性
推荐编译器版本:GCC 13
__cplusplus:待定
编译选项:-std=c++23或-std=gnu++2b
1 关键字
1.1 consteval
编译器版本
:GCC 12
文档链接
:
P1938R3: if consteval
新增版本:C++20,可查看C++20新特性进行回顾
扩展适用范围,新增支持if表达式
前版本存在的问题:
#include <type_traits>
// 立即函数
consteval int f(int i)
{
return i;
}
constexpr int g(int i)
{
// 标准库中,is_constant_evaluated函数声明为constexpr
if constexpr (std::is_constant_evaluated())
{
// 当前为常量语境时执行
return f(i) + 1; // error,g()函数并非常量表达式
}
else
{
return 42;
}
}
// 立即函数
consteval int h(int i)
{
return f(i) + 1;
}
在语义角度看,既然if constexpr (std::is_constant_evaluated())已经限定了常量语境了,为何还不能调用立即函数?但是constexpr和consteval的底层机理并不一致,所以并不相通。
因此为解决这个问题,新增 if consteval { } 来代替 if(std::is_constant_evaluated())。
语法:
if consteval {}
consteval前后不能有圆括号,后面必须接花括号
新版例子:
#include <type_traits>
// 立即函数
consteval int f(int i)
{
return i;
}
constexpr int g(int i)
{
// if consteval后面的花括号不可省略
if consteval // 否定形式:not consteval 或 ! consteval
{
return f(i) + 1;
}
else
{
return 42;
}
}
1.2 auto
1.2.1 新增支持数组指针的引用类型
编译器版本
:GCC 12
文档链接
:
CWG 2397: auto specifier for pointers and references to arrays
例子:
int main()
{
int a[3];
auto (*p)[3] = &a;
return 0;
}
1.2.2 代替decay-copy语义
编译器版本
:GCC 12
文档链接
:
P0849R8: auto(x): decay-copy in the language
不太严谨地说,decay-copy语义其实是将一个变量复制一份生成其对应的prvalue。新特性下,auto将可以表示decay-type,auto(x)代替decay-copy语义。
例子:
#include <iostream>
struct A
{
A() {}
// 拷贝构造函数
A(const A&) {}
// 移动构造函数
A(A&&) {}
};
void f(A&)
{
std::cout << "A(A&)" << std::endl;
}
void f(A&&)
{
std::cout << "A(A&&)" << std::endl;
}
void h()
{
A a;
std::cout << "===11====" << std::endl;
// 输出A(A&),因为a是左值
f(a);
std::cout << "===22====" << std::endl;
// 输出A(A&&),因为a是左值,所以先调用A的拷贝构造函数创建prvalue
f(A(a));
std::cout << "===33====" << std::endl;
// 输出A(A&&),因为a是左值,所以先调用A的拷贝构造函数创建prvalue,此处auto表示A类型
f(auto(a));
std::cout << "===44====" << std::endl;
// 输出A(A&&),因为a是左值,所以先调用A的拷贝构造函数创建prvalue
// 里层的auto表示A类型,返回一个prvalue;而因为里层已经prvalue,外层的auto则是什么都不干
f(auto(auto(a))); //
std::cout << "===55====" << std::endl;
// 等号左边的auto表示A*类型,等号右边的auto表示A类型
auto t = new auto(a);
// 此处的auto表示A类型
f(auto(*t)); // 编译成功
f(auto(t)); // 编译错误,因为auto表示A类型,但是t是A*类型
delete t;
}
1.3 volatile
编译器版本
:GCC 13
文档链接
:
P2327R1: De-deprecating volatile compound operations
还原C++20中volatile弃用的特性
略
1.4 constexpr
编译器版本
:GCC 13
文档链接
:
P2448R2: Relaxing some constexpr restrictions
新增版本:C++11
放宽constexpr限制,constexpr函数内可以使用控制流语句和变量初始化操作以及运行时的条件(如if、switch、for、while等),以便更好适应实际应用场景。
例子:
#include <iostream>
constexpr void func(int i)
{
int a = i * i + i;
if(i == 1)
{
std::cout << "111111" << std::endl;
}
else
{
std::cout << "222222" << std::endl;
}
switch(a)
{
case 0:
std::cout << "a = 0" << std::endl;
break;
case 1:
std::cout << "a = 1" << std::endl;
break;
}
}
int main()
{
func(10);
return 0;
}
1.5 char8_t
编译器版本
:GCC 13
文档链接
:
P2513R3: char8_t Compatibility and Portability Fix
新增版本:C++20
主要问题:char8_t类型在不同的平台和编译器之间可能有不同的实现方式和语义,在跨平台开发时,可能会有兼容性和可移植性问题。因此C++23即修复该问题。
如下例:
extern const char* a = u8"a"; // Works in C (using default extensions), broken in C++20
extern const char b[] = u8"b"; // Works in C, broken in C++20
extern const unsigned char* c = u8"c"; // Works in C (using default extensions), broken in C++20
extern const unsigned char d[] = u8"d"; // Works in C, broken in C++20
修改的结果:
①char8_t是基本的字符类型
②确定char8_t和char类型之间的关系(char8_t是utf8字符数据,char是本地字符数据)
③将char8_t类型的标准库扩展到所有操作系统和编译器上
例子:
extern const char* a = u8"a"; // 依然编译不通过
extern const char b[] = u8"b"; // C++23可运行
extern const unsigned char* c = u8"c"; // 依然编译不通过
extern const unsigned char d[] = u8"d"; // C++23可运行
char8_t与char是不同的两种类型,其对应的指针类型也就不能直接赋值,类似于int*和char*。
1.6 wchar_t
编译器版本
:GCC 已实现
文档链接
:
P2460R2: Relax requirements on wchar_t to match existing practices
放宽wchar_t要求,使其更加灵活,以匹配已有的实践经验。原来的标准是所有宽编码的所有字符使用一个wchar_t存储,但是实际上在windows(MSVC)一个wchar_t表示一个UTF-16字符。
结果:将已有的实践经验标准化。
2 语义语法
2.1 size_t字面量
编译器版本
:GCC 11
文档链接
:
P0330R8: Literal Suffix for (signed) size_t
新增zu作为std::size_t的字面量后缀
例子:
#include <iostream>
int main()
{
auto a = 10zu;
auto b = 1u;
std::cout << std::boolalpha;
std::cout << std::is_same<decltype(a), decltype(b)>::value << std::endl; // 输出false
std::cout << std::is_same<decltype(a), long long>::value << std::endl; // 输出false
std::cout << std::is_same<decltype(a), unsigned long long>::value << std::endl; // 输出false
std::cout << std::is_same<decltype(a), std::size_t>::value << std::endl; // 输出true
return 0;
}
2.2 lambda表达式的空圆括号
编译器版本
:GCC 11
文档链接
:
P1102R2: Make () more optional for lambdas
lambda表达式中空的圆括号可不写,扩展适用范围(原本是不支持的),包括:
①模板参数、②constexpr、③mutable、④consteval、⑤异常规范和noexcept、⑥属性列表、⑦返回类型、⑧requires
代码例子:
int main()
{
// noexcept前面的圆括号
auto a = [] noexcept {}; // C++23以前正确写法:[] () noexcept {}
// constexpr前面的圆括号
auto b = [] constexpr {}; // C++23以前正确写法:[] () constexpr {}
// 返回类型
auto c = [] -> void {}; // C++23以前正确写法:[] () -> void {}
return 0;
}
2.3 标识符支持Unicode标准附录31
编译器版本
:GCC 12
文档链接
:
P1949R7: C++ Identifier Syntax using Unicode Standard Annex 31
略
2.4 允许属性重复
编译器版本
:GCC 11
文档链接
:
P2156R1: Allow Duplicate Attributes
略
2.5 向下类型转换为bool类型
编译器版本
:GCC 9
文档链接
:
P1401R5: Narrowing contextual conversions to bool
主要是新增支持static_assert和if constexpr表达式中。
2.6 规范行尾反斜杆
编译器版本
:GCC 已支持
文档链接
:
P2223R2: Trimming whitespaces before line splicing
规范反斜杆换行,解决多种编译器不一致的问题。
如例子:
#include <iostream>
int main()
{
int i = 1
// \
+ 42
; // 按照语义,注释行末尾加反斜杆,下一行应当也是注释行
std::cout << i << std::endl; // MSVC输出43,GCC和Clang输出1
return 0;
}
此处规范后,输出i为1
2.7 取消混合的字符串字面量连接语法
编译器版本
:GCC 已支持
文档链接
:
P2201R1: Mixed string literal concatenation
在标准层面不再支持混合字符串字面量的连接语法
例子:
int main()
{
auto a = L"" u""; // 不再支持
auto a = L"" u8""; // 不再支持
return 0;
}
2.8 非静态数据成员的地址
编译器版本
:GCC 已支持
文档链接
:
P1847R4: Make declaration order layout mandated
明确规定,
类的非静态、大小非0的数据成员,声明顺序越往后,其偏移地址越大
。最初可追溯到C++03标准不够明确的表述,但其实各大编译器都非常默契地实现了。
不再举例。
2.9 移除垃圾回收的支持
编译器版本
:GCC 12
文档链接
:
P2186R2: Removing Garbage Collection Support
于C++11添加最小化的支持,可自行回顾,
文档链接
:
N2670: Minimal Support for Garbage Collection and Reachability-Based Leak Detection (revised)
关于垃圾回收的工具,可以自行搜索Boehm GC(可能某些混合使用C++和C#的unity游戏引擎的游戏也依赖于这个库),或者参考其他的一些用C++实现的支持垃圾回收的虚拟机。
移除“安全派生指针”的概念,具体概念可追溯C++11的新特性。
2.10 简化隐式移动语义
编译器版本
:GCC 13
文档链接
:
P2266R3: Simpler implicit move
一个表达式是xvalue(消亡值)的条件(之一):
①符合移动条件的变量
②无论是显示和隐式,返回类型是右值引用类型的函数的返回值
③被转换为右值引用
④以xvalue数组为操作数的下标操作
⑤访问一个xvalue对象的非引用类型的非静态数据成员
⑥.*成员指针表达式中,第一个操作数是xvalue,第二个操作数是数据成员
一般来说,有名字的右值引用视为lvalue(左值),没有名字的右值引用视为xvalue(消亡值),函数的右值引用无论是否有名字都视为lvalue(左值)
例子:编译器未支持,待续
2.11 this推导
编译器版本
:未支持
文档链接
:
P0847R7: Deducing this
成员函数第一个参数新增支持this,且支持模板推导
例子1-使用:
struct X
{
void foo(this X const& self, int i) {}
template <typename Self>
void bar(this Self&& self) {}
};
struct D : X { };
void ex(X& x, D const& d)
{
x.foo(42); // self绑定到x, i参数值42
x.bar(); // 函数参数Self类型推导为X&, 实际调用形式是X::bar<X&>
move(x).bar(); // 函数参数Self类型推导为X, 实际调用形式是X::bar<X>
d.foo(17); // self绑定到d
d.bar(); // 函数参数Self类型推导为D const&, 实际调用形式是X::bar<D const&>
}
例子2-覆盖:
struct B2
{
virtual void f() {}
virtual void g(this B2 const&, int) {}
};
struct D2 : B2
{
void f() override {} // 编译通过
void f(this D2 const&) override {} // 编译报错
void g(int) const& override {} // 编译报错
void g(this D2 const&, int) override {} // 编译通过
};
例子3-重载:
struct B3
{
virtual void f() {}
};
struct D3 : B3
{
void f(this D3&) {} // 编译通过,但并不是覆盖B3::f
};
例子4-代码优化:
//C++23前:->运算符需要写2个函数,用于区别是否const语义
template <typename T>
class Optional1
{
constexpr T* operator->()
{
return std::addressof(this->m_value);
}
constexpr T const* operator->() const
{
return std::addressof(this->m_value);
}
int m_value;
};
//新写法:->运算符只需写一个函数
template <typename T>
class Optional2
{
template <typename Self>
constexpr auto operator->(this Self&& self)
{
return std::addressof(self.m_value);
}
int m_value;
};
例子5-CRTP设计模式:
//C++23前:CRTP设计模式
template <typename Derived>
struct add_postfix_increment1
{
Derived operator++(int)
{
auto& self = static_cast<Derived&>(*this);
Derived tmp(self);
++self;
return tmp;
}
};
struct SomeType1 : add_postfix_increment1<SomeType1>
{
SomeType1& operator++() { /*......*/ }
};
//新写法:CRTP设计模式
struct add_postfix_increment2
{
template <typename Self>
auto operator++(this Self&& self, int)
{
auto tmp = self;
++self;
return tmp;
}
};
struct SomeType2 : add_postfix_increment2
{
SomeType2& operator++() { /*......*/ }
};
例子6-建造者设计模式:
#include <type_traits>
// C++23前:建造者设计模式
template <typename D=void>
class Builder1
{
using Derived = std::conditional_t<std::is_void_v<D>, Builder1, D>;
Derived& self()
{
return *static_cast<Derived*>(this);
}
public:
Derived& a() { /* ... */; return self(); }
Derived& b() { /* ... */; return self(); }
Derived& c() { /* ... */; return self(); }
};
struct Special1 : Builder1<Special1>
{
Special1& d() { /* ... */; return *this; }
Special1& e() { /* ... */; return *this; }
};
// C++23新特性:建造者设计模式
struct Builder2
{
template <typename Self>
Self& a(this Self&& self) { /* ... */; return self; }
template <typename Self>
Self& b(this Self&& self) { /* ... */; return self; }
template <typename Self>
Self& c(this Self&& self) { /* ... */; return self; }
};
struct Special2 : Builder2
{
Special2& d() { /* ... */; return *this; }
Special2& e() { /* ... */; return *this; }
};
int main()
{
Builder1().a().b().a().b().c();
Special1().a().d().e().a();
Builder2().a().b().a().b().c();
Special2().a().d().e().a();
return 0;
}
2.12 更改lambda中的作用域
编译器版本
:未支持
文档链接
:
P2036R3: Change scope of lambda trailing-return-type
为消除语义冲突,更改为只有lambda主体部分能够访问捕获列表的参数
核心问题如下例子:
double x;
auto a = [x=1](decltype((x)) y){ return x; };
这个例子可产生4种语义:
①lambda中的x是double&类型。这将导致a(100)报错。
②lambda中的x是int&类型。这将导致a(100.012)报错
③lambda中的x是int const&类型。
④语法错误。
实际是何种语义,全凭编译器决定。
因此,新特性将统一规范lambda。
因编译器未支持,代码例子待续。
2.13 多维下标运算符
编译器版本
:GCC 12
文档链接
:
P2128R6: Multidimensional subscript operator
下标运算符支持变长参数,以支持多维访问
用法例子:
#include <iostream>
#include <vector>
struct X
{
std::vector<int> v;
// 重载下标运算符,以实现数组元素求和
template<typename _Index>
int operator[](_Index &&index)
{
return v[index];
}
template<typename _T1, typename ... _T2>
int operator[](_T1 &&index, _T2 &&...indexs)
{
return v[index] + (*this)[std::forward<_T2>(indexs)...];
}
};
int main()
{
X x;
x.v = {1, 2, 3, 4, 5, 6, 7};
int sum = x[1, 2, 3, 6];
std::cout << sum << std::endl; // 输出16,因为2+3+4+7=16
return 0;
}
2.14 constexpr函数中常量语境下的变量
编译器版本
:GCC 12
文档链接
:
P2242R3: Non-literal variables (and labels and gotos) in constexpr functions
增强编译优化,允许在constexpr函数中的常量环境下定义变量,只要不影响返回值,变量相关的代码将被优化。
例子:
#include <iostream>
template<typename T>
constexpr bool f()
{
// 常量环境下
if (std::is_constant_evaluated())
{
// ...
return true;
}
else
{
T t; // 定义非常量对象,这将被忽略
t(); // 依然编译通过
// ...
return true;
}
}
struct nonliteral
{
nonliteral()
{
std::cout << "=======" << std::endl;
}
void operator()()
{
std::cout << "===operator()====" << std::endl;
}
};
// 常量语境下的调用,只要f()函数内部的变量不影响返回值,则编译通过
static_assert(f<nonliteral>());
2.15 字符集与字符编码
编译器版本
:GCC 10
文档链接
:
P2314R4: Character sets and encodings
新特性将支持以下上下文中使用unicode编码:
⑴asm内联汇编声明语句的编译环境
⑵#include文件名
⑶语言关联
⑷operator “”
⑸#line指令
⑹nodiscard和deprecated属性的提示文本
⑺#error和static_assert的提示文本
⑻__FILE__和__func__的字符串名称
⑼std::typeinfo::name()
⑽字符字面量或字符串字面量
⑾用户自定义的字面量
2.16 一致的字符字面量编码
编译器版本
:已支持
文档链接
:
P2316R2: Consistent character literal encoding
无需支持字符集,即可支持完全可移植的编码,并可以在不同的机器上通过编译器的编码转换而保持相同的编码。当前支持unicode编码转义文字。
2.17 初始化声明新增支持别名声明
编译器版本
:GCC 12
文档连接:
P2360R0: Extend init-statement to allow alias-declaration
选择结构、基于范围的循环结构 的初始化语句新增支持别名声明
例子:
#include <iostream>
int main()
{
int a[10] = {0};
int b = 100;
if(typedef int T; b < 100)
{
/* ... */
}
for(using T = int; T v : a)
{
std::cout << v << std::endl;
}
return 0;
}
2.18 lambda表达式新增属性支持
编译器版本
:GCC 9
文档链接
:
P2173R1: Attributes on lambda-expressions
例子:
auto lm = [] [[nodiscard]] ()->int { return 42; };
2.19 复合语句末尾的标签
编译器版本
:GCC 13
文档链接
:
P2324R1: Labels at the end of compound statements(C compatibility)
C++与C的标签还存在不兼容的地方,标签可以附加到所有的语句,但是C++不能将标签放到复合语句的末尾。
例子:
void foo(void)
{
first: // 这里的标签C++和C都支持
int x;
second: // 这里的标签C++和C都支持
x = 1;
last: // 这里最末尾的标签C支持,但C++不支持
}
int main()
{
int a = 10;
// 这里的标签C++支持,但C不支持,C需要用花括号
if(a > 10)
test_label1: int x;
// 新版写法:
if(a > 10)
{
test_label2:
int x;
}
return 0;
}
2.20 重写==和!=运算符
编译器版本
:GCC 13
文档链接
:
P2468R2: The Equality Operator You Are Looking For
重载operator==(A, B)时
①如果未重载operator!=(A, B),则对operator==(B, A)、operator!=(A, B)、operator!=(B, A)生效。
②如果已重载operator!=(A, B),则对operator==(B, A)、operator!=(A, B)、operator!=(B, A)不生效。
③如果已重载operator==(B, A)、operator!=(B, A),则优先调用operator==(B, A)、operator!=(B, A)。
但对于operator!=(A, B)没有这样的效果
代码如下:
#include <type_traits>
#include <iostream>
struct A {};
template<typename T>
bool operator==(A, T) // #1
{
std::cout << "bool operator==(A, T)" << std::endl;
return true;
}
bool a1 = 0 == A(); // ok,调用#1
bool a2 = 0 != A(); // ok,调用#1
bool a3 = A() == 0; // ok,调用#1
bool a4 = A() != 0; // ok,调用#1
template<typename T>
bool operator!=(A, T) // #2
{
std::cout << "bool operator!=(A, T)" << std::endl;
return true;
}
bool a5 = 0 == A(); // 编译错误,没有找到operator==(int, A)
bool a6 = 0 != A(); // 编译错误,没有找到operator!=(int, A)
bool a7 = A() == 0; // ok,调用#1
bool a8 = A() != 0; // ok,调用#2
struct B
{
bool operator==(const B&) // #3
{
std::cout << "bool B::operator==(const B&)" << std::endl;
return true;
}
};
struct C : B
{
C() {}
C(B) {}
bool operator!=(const B&) // #4
{
std::cout << "bool C::operator!=(const B&)" << std::endl;
return true;
}
};
bool c1 = B() == C(); // ok,调用#3
bool c2 = C() == B(); // C对象的operator==(const B&) 和 B对象的operator==(const B&)冲突
// GCC 13编译不会报错,但有警告
struct D {};
template <typename T>
bool operator==(D, T) // #5
{
std::cout << "bool operator==(D, T)" << std::endl;
return true;
}
inline namespace N
{
template <typename T>
bool operator!=(D, T) // #6
{ return true; }
}
bool d1 = 0 == D(); // 编译错误,当前命名空间下,operator==(D, int)和operator!=(D, T)都已重载
// 找不到operator==(int, D)的重载实现
2.21 移除无法编码的宽字符字面量和多字符宽字符字面量
编译器版本
:GCC 13
文档链接
:
P2362R3: Remove non-encodable wide character literals and multicharacter wide character literals
这篇文档不是不再支持宽字符字面量语法,而是建议如何移除无法编码的情况,使代码更加健壮和易读。
例子1:
#include <iostream>
int main()
{
std::cout << 'ab' << std::endl;// a是0x61,b是0x62,所以输出24930(即0x6162)
// 因此,建议拆分成'a'和'b'
return 0;
}
例子2:
#include <iostream>
int main()
{
// 类似这种unicode字符集的宽字符,可以使用L前缀来表示宽字符
std::wcout << L'\u0001F525' << std::endl;
return 0;
}
2.22 新的八进制、十六进制和universal-character-name的转义序列
编译器版本
:GCC 13
文档链接
:
P2290R3: Delimited escape sequences
universal-character-name转义序列:即使用
4或8个十六进制数字
或
16或32位
来表示的unicode标量值
新的转义字符表示方法:
const char *ch1 = "\u{0001F1F8}"; // unicode
const char *ch2 = "\o{053724}"; // 八进制
const char *ch3 = "\x{0001F1F8}"; // 十六进制
2.23 新增unicode编码的字符序列的转义序列
编译器版本
:GCC未实现
文档链接
:
P2071R2: Named universal character escapes
新增 \N{名称} 语法来标识标准unicode字符序列,暂时只适用于字符和字符串。
例子:
const char *ch = "\N{0001F1F8}\N{U+000100}";
2.24 常量表达式中使用未知的指针和引用
编译器版本
:GCC 未实现
文档链接
:
P2280R4: Using unknown pointers and references in constant expressions
本质的改动,就是把
可以在编译期计算出结果的运行期变量
在编译期计算出来而且不用写constexpr等修饰词。
例子:
int func1(int a)
{
return a + a;
}
int func2(int *a)
{
return (*a) * (*a);
}
int main()
{
int a = 10;
int b = 2;
int c = func1(b); // 此处因为b可在编译期计算得到2,所以c的结果可直接在编译期得到4
int d = func2(&a); // 此处因为b可在编译期计算得到10,所以c的结果可直接在编译期得到100
return 0;
}
2.25 static operator()
编译器版本
:GCC 13
文档链接
:
P1169R4: static operator()
目前的括号运算符重载函数都是以非静态成员函数的方式实现,而对于STL中的接口,需要传入带有括号运算符函数的类型(例如std::less等),如果该类型的括号重载函数没有内联,那么在使用时还得必须创建对应类型的对象,也就需要使用一个额外的寄存器存入对象的this指针。
例子:
#include <vector>
#include <algorithm>
struct A
{
static bool operator()(int a, int b)
{ return a > b; }
};
int main()
{
std::vector<int> v;
std::sort(v.begin(), v.end(), std::greater<int>());
using CmpType = decltype(&A::operator()); // CmpType被推导为std::function<bool(int, int)>
std::sort(v.begin(), v.end(), CmpType()); // C++23支持的形式
return 0;
}
汇编的结果可自行查看,静态的operator()相比于非静态的operator(),少了一层偏移量的设定。
2.26 新增浮点数类型别名
编译器版本
:GCC 13
文档链接
:
P1467R9: Extended floating-point types and standard names
新增类型:std::float16_t、std::float32_t、std::float64_t、std::float128_t
分别代表16位、32位、64位、128位的浮点数,不再赘述
2.27 从继承的构造函数中推导模板参数
编译器版本
:未实现
文档链接
:
P2582R1: Wording for class template argument deduction from inherited constructors
在C++17中就可以利用构造函数来推导模板参数(请自行回顾),在C++23中,这种办法扩展到了继承机制,可以从基类继承过来的构造函数中推导模板参数
例子:
template<typename T>
struct B
{
B(T) {}
};
template<typename T>
struct C : public B<T>
{
using B<T>::B;
};
template<typename T>
struct D : public B<T>
{ };
C c(42); // 编译通过,c类型推导为C<int>
D d(42); // 编译不通过,没有引入可推导的构造函数
B(int) -> B<char>; // 显式声明推导类型
C c2(42); // 编译通过,c2对象被推导为C<char>
template<typename T>
struct E : public B<int>
{
using B<int>::B;
};
/**
* 编译不通过,因为上面B(int)已经显式声明推导类型是B<char>了
* E中using的是B<int>类型的构造函数
* 没有引入可推导的路径
*/
E e(42);
E<int> e2(42); // 编译通过
template<typename T, typename U, typename V>
struct F
{
F(T, U, V) {}
};
template<typename T, typename U>
struct G : F<U, T, int>
{
using G::F::F;
};
G g(true, 'a', 1); // 编译通过,g对象推导为G<char, bool>
2.28 支持UTF8作为可移植的源文件编码
编译器版本
:GCC 13
文档链接
:
P2295R6: Support for UTF-8 as a portable source file encoding
略
2.29 显式生命周期管理
编译器版本
:编译器未实现
文档链接
:
P2590R2: Explicit lifetime management
个人理解,思想上类似一个内存池(并非对象池的那种内存池),当需要创建对象(无论什么类型)时,都可以直接从同一个内存池中获得内存地址。
因编译器没实现,就不多说了。
2.30 static operator[]
编译器版本
:GCC 13
文档链接
:
P2589R1: static operator[]
operator[]增加支持static修饰,略
2.31 允许constexpr函数中定义static constexpr变量
编译器版本
:GCC 13
文档链接
:
P2647R1: Permitting static constexpr variables in constexpr functions
比较简单,直接看例子:
constexpr int func(int a, int b)
{
static constexpr int v = 100; // C++23起支持static constexpr
return a + b + v;
}
2.32 consteval向上传导
编译器版本
:编译器未实现
文档链接
:
P2564R3: consteval needs to propagate up
待续
2.33 更有意义的export
编译器版本
:编译器未实现
文档链接
:
P2615R1: Meaningful exports
待续
2.34 修复基于范围的for循环的问题
编译器版本
:编译器未实现
文档链接
:
P2718R0: Wording for P2644R1 Fix for Range-based for Loop
待续
3 预处理指令
3.1 elifdef和elifndef
编译器版本
:GCC 12
文档链接
:
P2334R1: Add support for preprocessing directives elifdef and elifndef
新增预处理命令,用于ifdef和ifndef的else部分的条件。
例子:
#ifdef MY_IF1
#elifdef MY_IF2
#endif
#ifndef MY_IF1
#elifndef MY_IF2
#endif
#ifdef MY_IF1
#elifndef MY_IF2
#endif
#ifndef MY_IF1
#elifdef MY_IF2
#endif
3.2 warning
编译器版本
:GCC 13
文档链接
:
P2437R1: Support for #warning
用于在预处理阶段放出警告
语法格式:#warning [text]
例子:
#warning "ahhahaha"
4 属性
4.1 assume
编译器版本
:GCC 13
文档链接
:
P1774R8: Portable assumptions
新增编译器指令,用于提示编译器某一个假设条件一定成立,可以忽略某些可能发生的错误检查,以便编译器更好地优化代码
语法格式:[[assume(expr)]]
其中expr为可得到bool的表达式
用法例子:
[[assume(expr1, expr2)]]; // Error
[[assume((expr, expr2))]]; // OK
[[assume(x = 1)]]; // Error
[[assume(x == 1)]]; // OK
[[assume((x = 1))]]; // OK
优化例子:
int f1(int x)
{
[[assume(x >= 0)]]; // 假设x是不小于0的
return x / 32; // 可能会省略负值的处理
}
int f11(int x)
{
return x / 32; // 可能会省略负值的处理
}
int f2(int y)
{
[[assume(++y == 43)]]; // 假设y+1等于43
return y; // 该return语句可能被替换为return 42;
}
int f22(int y)
{
return y; // 该return语句可能被替换为return 42;
}
对应的汇编代码(开启-O2编译选项,可以看得到优化效果)
f1(int):
mov eax, edi
sar eax, 5
ret
f11(int):
test edi, edi
lea eax, [rdi+31]
cmovns eax, edi
sar eax, 5
ret
f2(int):
mov eax, 42
ret
f22(int):
mov eax, edi
ret
5 标准库
推荐网站:https://www.apiref.com/cpp-zh/cpp/header.html
不再列举