我上一篇文章 《C++ 20:四大巨头》首先概述了概念(Concepts)、范围(Ranges)、协程(Coroutines)和模块化(Modules)。当然,C++ 20 还提供了更多的功能。今天,我们将继续讲述关于核心语言的概述。
核心语言
当你看到这张图时,你就明白我想要介绍的功能了。
三元比较运算符<=>
三元比较运算符 <=> 通常被称为宇宙飞船运算符(spaceship operator)。该宇宙飞船运算符可用于确定两个值 A 和 B 的大小,是 A<B、A=B 还是 A>B。
编译器可以自动生成三元比较运算符。我们只需设置 default 就可以使用它了。在这种情况下,我们将得到全部共六个比较运算符,如 ==、!=、<、<=、> 和 >= 。
#include <compare>
struct MyInt {
int value;
MyInt(int value): value{value} { }
auto operator<=>(const MyInt&) const = default;
};
复制代码
默认运算符 <=> 可以执行字典序比较,它按照基类从左到右的顺序,并按字段声明顺序对非静态成员进行比较。下面是一个摘自微软博客非常复杂的例子:用火箭科学简化你的代码:C++ 20 的宇宙飞船运算符。
struct Basics {
int i;
char c;
float f;
double d;
auto operator<=>(const Basics&) const = default;
};
struct Arrays {
int ai[1];
char ac[2];
float af[3];
double ad[2][2];
auto operator<=>(const Arrays&) const = default;
};
struct Bases : Basics, Arrays {
auto operator<=>(const Bases&) const = default;
};
int main() {
constexpr Bases a = { { 0, 'c', 1.f, 1. },
{ { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
constexpr Bases b = { { 0, 'c', 1.f, 1. },
{ { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
static_assert(a == b);
static_assert(!(a != b));
static_assert(!(a < b));
static_assert(a <= b);
static_assert(!(a > b));
static_assert(a >= b);
}
复制代码
我认为,这个代码片段中最复杂的内容不是宇宙飞船运算符,而是使用了聚合初始化来初始化 Base。聚合初始化本质上意味着:如果所有成员都是公共的,我们可以直接初始化类类型(class、struct 或 union)的成员。在这种情况下,我们可以使用带括号的初始化列表,如上例所示。这只是一个简化示例。请阅读此处的详细信息:聚合初始化。
字符串文本作为模版参数
在 C++ 20 之前,我们不能使用字符串作为非类型模板参数。在 C++ 20 中,我们可以使用了。其思想是使用标准定义的 basic_fixed_string 类型, basic_fixed_string 具有一个 constexpr 构造函数。constexpr 构造函数允许它在编译时实例化固定的字符串。
template<std::basic_fixed_string T>
class Foo {
static constexpr char const* Name = T;
public:
void hello() const;
};
int main() {
Foo<"Hello!"> foo;
foo.hello();
}
复制代码
constexpr 虚拟函数
由于动态类型是未知的,因此无法在常量表达式中调用虚拟函数。C++ 20 将沿用这个限制。
指定初始化值
首先,让我先写一个聚合初始化的简单示例,如下所示:
#include <iostream>
struct Point2D{
int x;
int y;
};
class Point3D{
public:
int x;
int y;
int z;
};
int main(){
std::cout << std::endl;
Point2D point2D {1, 2};
Point3D point3D {1, 2, 3};
std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
std::cout << std::endl;
}
复制代码
我认为没有必要对这个程序进行解释了。下面是这个程序的输出:
显式的声明比隐式的要好。我们来看看这是什么意思。在程序 aggregateInitialisation.cpp 中,初始化是非常容易出错的,因为我们可能在自己不注意的情况下变换构造函数参数的顺序。下面所示的指定初始化值是从 C99 开始引入的。
#include <iostream>
struct Point2D{
int x;
int y;
};
class Point3D{
public:
int x;
int y;
int z;
};
int main(){
std::cout << std::endl;
Point2D point2D {.x = 1, .y = 2};
Point3D point3D {.x = 1, .y = 2, .z = 2};
std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
std::cout << std::endl;
}
复制代码
Point2D 和 Point3D 实例的参数是被显式声明的。该程序的输出与程序 aggregateInitialisation.cpp 的输出相同。注释掉的行(1)和(2)非常有趣。行(1)将会产生错误,因为指定元素的顺序与其声明顺序不匹配。 y 的指定值在行(2)中是缺失的。在这种情况下,y 将被初始化为 0,类似于使用带括号的初始化列表 {1、0、3} 对其进行初始化。
各种 Lambda 的改进
在 C++ 20 中, Lambda 将会有很多的改进。
如果你想了解更多变更详细信息,请浏览 Bartek 发表的关于 C++ 17 和 C++ 20 中的 Lambda 改进的文章,或者等待我编写更详细的文章。不管怎样,在此,我们将介绍 Lambda 两个有趣的变化。
struct Lambda {
auto foo() {
return [=] { std::cout << s << std::endl; };
}
std::string s;
};
struct LambdaCpp20 {
auto foo() {
return [=, this] { std::cout << s << std::endl; };
}
std::string s;
};
复制代码
在 C++ 20 中,隐式 [=] 捕获器在 Lambda 结构中复制会引起一个弃用警告。当我们通过复制 [=, this] 显式捕获 this 对象时,在 C++ 20 中, 我们将不会在收到弃用警告。
你对模版 Lambda 的第一印象可能和我的一样:我们为什么需要模版 Lambda ?当我们使用 C++ 14 中的 { return x; } 编写一个泛型 Lambda 时,编译器会自动生成一个带有模板调用运算符的类:
template <typename T>
T operator(T x) const {
return x;
}
复制代码
有时,我们想定义一个仅适用于特定类型(如 std::vector )的 Lambda。此时,模板 Lambda 可以帮我们解决这个问题。除了类型参数,我们还可以使用概念( concept ):
auto foo = []<typename T>(std::vector<T> const& vec) {
};
复制代码
新属性:[[likely]] 和 [[unlikely]]
使用 C++ 20,我们可以获取新的属性 [[likely]] 和 [[unlikely]] 。不管执行路径概率大小,这两个属性都允许它给优化器一个提示。
for(size_t i=0; i < v.size(); ++i){
if (unlikely(v[i] < 0)) sum -= sqrt(-v[i]);
else sum += sqrt(v[i]);
}
复制代码
consteval 和 constinit 声明符
新的声明符 consteval 可以创建一个即时函数。对于即时函数来说,对该函数的每次调用都必须生成编译时的常量表达式。即时函数是一个隐式的 constexpr 函数。
consteval int sqr(int n) {
return n*n;
}
constexpr int r = sqr(100);
int x = 100;
int r2 = sqr(x);
复制代码
由于 x 不是常数表达式,因此最终赋值时会导致错误 ,故 sqr(x) 无法在编译时执行。
constinit 可以确保具有静态存储持续时间的变量在编译时初始化。静态存储持续时间意味着在程序开始时分配对象,在程序结束时释放对象。在命名空间作用域中声明的对象(全局对象)、声明为 static 或 extern 对象都具有静态存储持续时间。
std::source_location
C++ 11 中有两个宏 LINE 和 FILE ,它们可用于在使用宏时获取信息。使用 C++ 20 ,source_location 类为我们提供了源代码的文件名、行号、列号和函数名等。下面是一个摘自 cppreference.com 的简短示例,它展示了第一种用法:
#include <iostream>
#include <string_view>
#include <source_location>
void log(std::string_view message,
const std::source_location& location = std::source_location::current())
{
std::cout << "info:"
<< location.file_name() << ":"
<< location.line() << " "
<< message << '\n';
}
int main()
{
log("Hello world!");
}
复制代码
接下来的安排?
这篇文章是有关核心语言中较小功能的第一篇概述。下一篇文章我将继续讲述 C++ 20 中库的特性。
原文链接:
C++ 20: The Core Language
评论 4 条评论