C++23新特性个人总结

文章目录

  • 1 关键字
    • 1.1 consteval
    • 1.2 auto
      • 1.2.1 新增支持数组指针的引用类型
      • 1.2.2 代替decay-copy语义
    • 1.3 volatile
    • 1.4 constexpr
    • 1.5 char8_t
    • 1.6 wchar_t
  • 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 预处理指令
    • 3.1 elifdef和elifndef
    • 3.2 warning
  • 4 属性
    • 4.1 assume
  • 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 

// 立即函数
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 

// 立即函数
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 

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 

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 

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 

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
    move(x).bar();  // 函数参数Self类型推导为X, 实际调用形式是X::bar

    d.foo(17);  // self绑定到d
    d.bar(); // 函数参数Self类型推导为D const&, 实际调用形式是X::bar
}

例子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 

// 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 
#include 

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 

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 


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 
#include 

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 

int main()
{
    std::cout << 'ab' << std::endl;// a是0x61,b是0x62,所以输出24930(即0x6162)
    // 因此,建议拆分成'a'和'b'
    return 0;
}

例子2:

#include 


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 
#include 

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
    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
D d(42); // 编译不通过,没有引入可推导的构造函数
B(int) -> B<char>; // 显式声明推导类型
C c2(42); // 编译通过,c2对象被推导为C

template<typename T> 
struct E : public B<int> 
{
    using B<int>::B;
};

/**
 * 编译不通过,因为上面B(int)已经显式声明推导类型是B了
 * E中using的是B类型的构造函数
 * 没有引入可推导的路径
 */ 
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

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
不再列举

你可能感兴趣的:(C++新特性,c++23,c++)