文章目录
显式对象形参(Explicit Object Parameters)
语法:
class Object {
public:
int GetId() { return id_; }
private:
int id_ = 0;
};
其中 GetId
可以改写为:
class Object {
public:
int GetId(this Object& self) { return self.id_; }
private:
int id_ = 0;
};
使用场景1: 重载运算符
class Object {
public:
Object(std::string name) : name_{std::move(name)} {}
// 此处有三个重载
const std::string& GetName() const& { return name_; }
std::string& GetName() & { return name_; }
std::string&& GetName() && { return std::move(name_); }
private:
std::string name_;
};
可以改写为:
class Worker {
public:
Worker(std::string name) : name_{ std::move(name) } {}
template <typename Self>
auto&& GetName(this Self&& self) {
return std::forward<Self>(self).name_;
}
private:
std::string name_;
};
进一步参考:
- C++ Weekly - Ep 326 - C++23’s Deducing
this
- C++23’s Deducing this: what it is, why it is, how to use it
- Daily bit(e) of C++ | Explicit object parameter (a.k.a. deducing this)
if consteval
语法:
if consteval { /* A */ } else { /* B */ }
语义:
如果语句是在常量表达式上下文中执行的,那么执行A,否则执行B。
#include <cassert>
consteval int f(int i) { return i; }
constexpr int g(int i) {
if consteval {
// 编译时计算
return f(i) + 1;
} else {
return 42;
}
}
int main() {
// 相同的函数调用在不同的上下文中有不同的行为
static_assert(g(1) == 2);
assert(g(1) == 42);
}
从上面的例子中可以看到, 相同的函数调用g(1)
, 在编译时运行会得到2
, 在运行时会得到42
.
多维数组下标(Multidimensional Subscript Operator)
#include <array>
#include <cassert>
#include <iostream>
template <typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d {
std::array<T, X * Y * Z> m{};
constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x) // C++23
{
assert(x < X and y < Y and z < Z);
return m[z * Y * X + y * X + x];
}
};
int main() {
// Multidimensional Subscript Operator
Array3d<int, 4, 4, 4> v;
std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
v[3, 2, 1] = 42;
std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}
lambada
表达式的特性
在C++23以前, lambda表达式可以有属性. 如下:
auto l = []() [[deprecated]] { return 42; };
在C++23中, lambda的调用可以有属性:
auto l = [] [[nodiscard]] () [[deprecated]] { return 42; };
[[maybe_unused]] int a = l();
进一步参考:
size_t
字面量后缀
已有的数字字面量后缀: U
, L
, UL
, LL
, ULL
.
新增的数字字面量后缀:
z
或Z
表示 有符号版本的std::size_t
uz
或UZ
表示std::size_t
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec{1, 2, 3, 4, 6};
for (auto i = 0uz; i < vec.size(); i++) {
std::cout << vec[i] << ' ';
}
static_assert(std::is_same<decltype(10uz), decltype(vec.size())>::value ==
true);
static_assert(std::is_same<decltype(10z), ssize_t>::value == true);
}
auto(x)
: decay copy
当前的auto
关键字用于类型推导
auto x = 42; // x is lvalue
C++23 中新增的auto(x)
,auto {x}
用来创建一个prvalue
的副本.
样例:
void process(int&& value) { std::cout << value << std::endl; }
void process_all(const std::vector<int>& data) {
for (auto& i : data) {
process(auto(i));
}
}
#elifdef
, #elifndef
, #warning
已有的的预处理指令:
#ifdef ID
是#if defined(ID)
的缩写#ifndef ID
是#if !defined(ID)
的缩写
C++23中新增的预处理指令:
#elifdef ID
是#elif defined(ID)
的缩写#elifndef ID
是#elif !defined(IDENTIFIER)
的缩写
#warning
指令用于生成编译器警告信息.
#warning "This is a warning message"
标记不可达代码(std::unreachable()
)
std::unreachable()
常被用于标记不可达代码. 如果代码执行到这里, 就会产生未定义的行为.
何谓"未定义行为"? 未定义行为是指程序在运行时可能产生任何结果, 包括崩溃, 输出错误, 或者其他不可预测的行为. 取决于编译器的实现.
#include <utility>
enum CaseOptions : int {
Option1 = 1,
Option2 = 2,
Option3 = 3,
};
int foo(CaseOptions i) {
switch (i) {
case Option1:
return 1;
case Option2:
return 2;
case Option3:
return 3;
default:
std::unreachable();
}
}
查看汇编结果
foo(CaseOptions):
mov eax, edi
ret
可以看到优化结果是直接返回了i
的值. 因为编译器认为default
分支是不可达的. 这种函数如果遇到了[1-3]之外的输入则会导致不可预测的结果.
int main(int, const char*[]) {
std::cout << foo(Option1) << std::endl;
std::cout << foo(static_cast<CaseOptions>(4)) << std::endl;
}
运行结果
1
685604928 <-- 未定义行为
假设(assume
)
C++23之前,不同编译器有不同的方式来标记假设. 例如:
__builtin_assume
(Clang)__assume
(MSVC and ICC)if (expr) {} else { __builtin_unreachable(); }
(GCC)
C++23统一了这些标记, 引入了std::assume
函数.
[[assume(expr)]]
这个标记用来告诉编译器一些前提条件, 以便优化代码.
未加修饰符号
int len(const char* ptr) {
if (ptr == nullptr) {
throw "oops";
}
return 0;
}
编译生成
.LC0:
.string "oops"
len(char const*):
test rdi, rdi
je .L3
xor eax, eax
ret
len(char const*) [clone .cold]:
.L3:
push rax
mov edi, 8
call __cxa_allocate_exception
xor edx, edx
mov esi, OFFSET FLAT:_ZTIPKc
mov QWORD PTR [rax], OFFSET FLAT:.LC0
mov rdi, rax
call __cxa_throw
加assume
之后:
int len(const char* ptr) {
[[assume(ptr != nullptr)]];
if (ptr == nullptr) {
throw "oops";
}
return 0;
}
编译生成
len(char const*):
xor eax, eax
ret
可以看出整个异常处理代码被优化掉了.
进一步参考:
Unicode已命名字符转义
C++23之前, 字符转义只能用于ASCII字符.
auto c {U'\u0100'}; // {Latin Capital Letter A with macron}
C++23引入了已命名字符转义
auto c {U'\N{Latin Capital Letter A with macron}'};
example
#include <cassert>
#include <codecvt>
#include <iostream>
#include <locale>
#include <string>
int main() {
auto c{U'\u0100'}; // {LATIN CAPITAL LETTER A WITH MACRON}
auto nc{U'\N{LATIN CAPITAL LETTER A WITH MACRON}'};
assert(c == nc);
std::u32string str;
str.push_back(c);
str.push_back(nc);
str.push_back('\n');
std::locale::global(std::locale(""));
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
std::cout << converter.to_bytes(str);
}
行拼接符('\'
)前的空白字符
在C++23以前, 如果行拼接符\
后有空白字符, 则会出现未定义的行为.
- Clang & GCC: 会忽略空白字符并警告, 输出
1
- MSVC: 不会忽略空白字符, 输出
2
C++23中, 行拼接符后的空白字符会被忽略.
#include <iostream>
int main() {
int i = 1
// 反斜杠后面有个空白符(space) \
+ 1
;
std::cout << i << '\n';
}