有人说C++ 23是一个小版本,相对C++11或者C++20而言,它就像站在巨人肩膀之上的小矮子。但实际上C++23版本正式克服了很多困难推出了比C++14规模要大且可以媲美C++17的改进。本篇文章,将主要对C++23的新特性做一个介绍。
1、if consteval 编译时优化
语法结构:
属性 (可选) if !(可选) consteval 复合语句
属性 (可选) if !(可选) consteval 复合语句 else 语句
C++17新增了基于编译和运行时条件,C++23在此基础上又做了提升,新增特性支持在明显语境下可以进行求值。
consteval int f(int i) { return i; }
constexpr int g(int i) {
if consteval {
return f(i) + 1;
} else {
return 10;
}
}
int main()
{
std::cout<<g(1)<<std::endl;
}
如上代码所示:编译阶段输出值为2,运行阶段为10.
2、显示this参数
C++23之前,调用C++函数this都是被当作隐藏指针的方式传递的。C++23开始,满足条件的函数this可以被显示传参,但使用过程中依旧需要遵循如下规范:
具体代码使用示例如下:
struct A
{
void f(this C& self,int iRate);
template<typename Self>
void g(this Self&& self);
}
3、多维下表运算符
C++23之前,下表运算符最多只能有一个,如果要实现多维数组的访问需要获取逐维数据的引用,一个三维坐标数据使用时需要按照如下方式编写:
Point3D[1][2][3]=10;
C++23开始,数组下标可以接受1个或者多个,实际使用时只需要重载operator操作符就可以实现,不得不说,这个改变最大的受益者是多维数组的使用。代码示例如下:
struct Point3D
{
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()
{
Point3D(int,4,3,2);
v[3, 2, 1] = 42;
}
4、std::size_t 字面量后缀
size_t整数后缀可以是如下字符的自由组合:u、U、z、Z。u、U表示无符号,z、Z表示有符号类型。代码示例如下:
int main()
{
std::array<std::size_t, 10> a;
// 使用 C++23 size_t 字面量的例子
for (auto i = 0uz; i != a.size(); ++i)
std::cout << (a[i] = i) << ' ';
std::cout << '\n';
// 自减循环的例子
for (std::size_t i = a.size(); i--;)
std::cout << a[i] << ' ';
std::cout << '\n';
}
5、定宽浮点类型
浮点型可以定义宽度了,根据不同的使用场景选择不同的定义类型。具体可以参考下图:
代码示例如下:
int main()
{
std::float64_t f = 0.1f64;
}
6、条件包含
预编译指令新增了两个组合命令,如下所示:
#elifdef 标识符同#elif defined 标识符”
#elifndef 标识符同#elif !defined 标识符”
#ifdef CPU
std::cout << "4: no1\n";
#elifdef GPU
std::cout << "4: no2\n";
#elifndef RAM
std::cout << "4: yes\n"; // 期待的块
#else
std::cout << "4: no!\n";
7、标记不可到达代码std::unreachable()
该特性用来标记不可能执行到的代码,类似于swich中的default,实际上,这个特性用在这里也比较合适,但唯一不足的是,如果用了此标记实际又触发了这个代码。会给程序带来不确定的行为。用或者不用仁者见仁了。常用代码示例如下:
switch(i)
{
case 1:
break;
case 2:
break;
default:
std::unreachable();
}
8、std::expected()
std::expected是C++23提供的一种全新的异常处理方式,使用时不会占用返回值通道,而且不会给忽略返回值检查。使用方法如下:
enum class parse_error
{
invalid_input,
overflow
};
auto parse_number(std::string_view& str) -> std::expected<double, parse_error>
{
const char* begin = str.data();
char* end;
double retval = std::strtod(begin, &end);
if (begin == end)
return std::unexpected(parse_error::invalid_input);
else if (std::isinf(retval))
return std::unexpected(parse_error::overflow);
str.remove_prefix(end - begin);
return retval;
}
int main()
{
auto process = [](std::string_view str)
{
std::cout << "str: " << std::quoted(str) << ", ";
if (const auto num = parse_number(str); num.has_value())
std::cout << "值: " << *num << '\n';
// 如果 num 没有值,那么解引用 num 会造成未定义行为,而
// num.value() 会抛出 std::bad_expected_access。
// num.value_or(123) 则使用指定的默认值 123。
else if (num.error() == parse_error::invalid_input)
std::cout << "错误:无效输入\n";
else if (num.error() == parse_error::overflow)
std::cout << "错误:溢出\n";
else
std::cout << "非预期!\n"; // 或调用 std::unreachable();
};
for (auto src : {"11", "11wew", "ere", "infsd"})
process(src);
}
如上,代码如果不能正常解析出数字则会按照预期设定的错误值进行处理。
9、std::move_only_function
这个新增的包装器被定义在<functional>头文件中,该包装器可以调用或者存储任何一个可以被存储并调用任何可构造或者调用的函数。如果存储对象为空,调用时会产生不可定义的行为。使用代码如下:
int main()
{
std::packaged_task<double()> packaged_task([](){ return 3.14159; });
std::future<double> future = packaged_task.get_future();
auto lambda = [task = std::move(packaged_task)]() mutable { task(); };
std::move_only_function<void()> function = std::move(lambda); // OK
std::cout << future.get();
}
此新增特性如果大家有好的理解欢迎指教。留下评论。
10、std::byteswap
逆转给定整数值中的字节:如十六进制数:FE CB。调用后会转换为:CB FE。
int main()
{
static_assert(std::byteswap('b') == 'b');
}
对于C++23新增特性很多编译器已经都能够进行支持,当然在C++23版本中规划的内容也不止上面说的这些,如果大家有需要补充或者对上述内容进行指正的欢迎大家留言。
4 参考
- EOF -