C++20新特性个人总结

目录

1  关键字

1.1  concept

1.2  requires

1.3  typename

1.4  explicit

1.5  constexpr

1.6  char8_t

1.7  consteval

1.8  co_await、co_yield、co_return

1.9  constinit

2  语法

2.1  位域变量的默认成员初始化

2.2  修改const限定的成员指针

2.3  允许lambda表达值按值捕获this

2.4  指定初始化

2.5  lambda表达式支持模板

2.6  从构造函数推导出模板参数类型

2.7  基于范围的for循环初始化

2.8  简化lambda的隐式捕获

2.9  ADL与不可见的模板函数

2.10  operator<=>

2.11  基于范围的for循环初始化

2.12  默认可构造可分配的无状态lambdas

2.13  专门的访问检查

2.14  constexpr函数的实例化

2.15  允许lambda在初始化捕获时进行包扩展

2.16  放宽结构化绑定,新增自定义查找规则

2.17  放宽基于范围的for循环,新增自定义范围方法

2.18  类类型的非类型模板参数

关于类类型的非类型模板参数的优化

类类型的非类型模板参数的条件(满足任意一个):

2.19  禁止使用用户自己声明的构造函数来进行聚合初始化

旧版的几个问题

解决方案

2.20  嵌套内联命名空间

2.21  约束声明的另一种办法

2.22  允许在常量表达式中使用dynamic_cast多台typeid

2.23  允许用圆括弧的值进行聚合初始化

2.24  new表达式的数组元素个数的推导

2.25  unicode字符串字面量

2.26  允许转换成未知边界的数组

2.27  聚合初始化推导类模板参数

2.28  隐式地将返回的本地变量转换为右值引用

2.29  允许default修饰运算符按值比较

2.30  非类型模板参数等效的条件

3  宏

4  属性

4.1  likely和unlikely

4.2  no_unique_address

4.3  nodiscard

5  弃用

5.1  lambda弃用使用[=]来隐式捕获this

5.2  比较运算符的改进

5.3  弃用下标表达式中的逗号操作符

后记


C++20

编译器版本:GCC 10

__cplusplus:待定

编译选项:-std=c++2a

1  关键字

1.1  concept

concept乃重头戏之一,用于模板库的开发。功能类似于C#的泛型约束,但是比C#泛型约束更为强大。

concept用于声明具有特定约束条件的模板类型。

例子:数值类型约束

#include <type_traits>
 
// 声明一个数值类型的concept
template<typename T>
concept number = std::is_arithmetic<T>::value; // 对模板类型T添加std::is_arithmetic<T>::value为true的约束,并对具有约束的新的类型声明number
 
// 使用具有约束的类型,调用该函数时,T类型必须符合std::is_arithmetic<T>::value等于true,否则编译报错
template<number T>
void func(T t)
{ }
 
// 调用
func<int>(10); // 正确,std::is_arithmetic<int>::value为true
func<double>(20.0); // 正确,std::is_arithmetic<double>::value为true
 
struct A
{ };
func<A>(A()); // 错误,std::is_arithmetic<A>::value为false

1.2  requires

单纯一个concept还不够强大,真正让concept起飞的是这个requires,concept结合requires之后,对模板类型参数的约束可以细致到类型成员变量、类型成员函数甚至其返回值等等。

例子:约束类型具有指定名称的成员变量、成员函数

#include <type_traits>
 
template<typename T>
concept can_run = requires(T t)
{
    std::is_class<T>::value; // T是一个类型
    t(); // T类型有重载括号运算符,且是无参的
    t.run(); // T类型具有run()成员函数
    std::is_same<decltype(t.run()), int>::value; // T类型的run()函数的返回值为int类型
}
 
// concepts类型使用
template<can_run T>
int func(T t)
{
    t();
    return t.run(); // run()函数的返回值已被限定为int类型,所以此处可直接返回
}
 
func<int>(10); // 错误,这不是一个class或struct
 
struct A
{
    void run() { }
}
func<A>(A()); // 编译错误,没有重载括号运算符
 
struct B
{
    void operator()() { }
}
func<B>(B()); // 编译错误,没有run()函数
 
struct C
{
    void operator()() { }
    void run() { }
}
func<C>(C()); // 编译错误,run()函数返回值不是int类型
 
struct D
{
    int operator()() { }
    int run() { return 0; } 
}
func<D>(D()); // 正确,编译通过

1.3  typename

typename主要两种用法:①模板类型声明②声明一个名称是类型名。此前为了解决冲突问题,功能②被大量地使用,新版本为了提高可读性,加强了编译的推导能力,简化typename在功能②的使用。

在一些地方,例如在某指定的上下文中只能推导为类型的地方,可不加typename。

例子:

// 函数的返回值,在全局范围内只可能是一种类型,所以可不加typename
template<class T> T::R f(); // OK, return type of a function declaration at global scope
 
// 作为函数的参数,
template<class T> void f(T::R); // Ill-formed (no diagnostic required), attempt to declare a void variable template
 
template<typename T>
struct PtrTraits
{
    typedef T* Ptr;
};
 
template<class T> 
struct S 
{
    using Ptr = PtrTraits<T>::Ptr; // OK, in a defining-type-id
 
    T::R f(T::P p) 
    { // OK, class scope
        return static_cast<T::R>(p); // OK, type-id of a static_cast
    }
 
    auto g() -> S<T*>::Ptr;// OK, trailing-return-type
};
 
template<typename T> void f() 
{
    void (*pf)(T::X); // Variable pf of type void* initialized with T::X
    void g(T::X); // Error: T::X at block scope does not denote a type
    // (attempt to declare a void variable)
}

1.4  explicit

新增bool参数,表示explicit本身的作用是否启用

例子:

struct A
{
    explicit(false)
    A(int) { }
};
struct B
{
    explicit(true)
    B(int) { }
};
 
A a = 10; // 正确
B b = 10; // 错误:将int类型转换为B类型

1.5  constexpr

①扩展适用范围,新增对虚函数的支持,用法与普通函数一致,不再赘述。

②禁止constexpr函数内使用try-catch语句块。不再赘述。

1.6  char8_t

为utf-8字符编码专门打造,以后就由char8_t类型接收utf-8字面量,而不再由char接收。

编译器未完全实现,待续。

1.7  consteval

编译器未实现,待续。

1.8  co_await、co_yield、co_return

协程三件套:co_await、co_yield、co_return

由于编译器未支持,详解待续。

1.9  constinit

用于强制常量进行初始化,不可动态初始化。

变量条件:静态 或 线程存储持续时间。thread_local修饰的变量可不进行初始化

例子:

const char * get_str1()
{
    return "111111";
}
constexpr const char * get_str2()
{
    return "222222";
}
 
const char *hahah = " hhahahaa ";
 
constinit const char *str1 = get_str2(); // 编译正确
constinit const char *str2 = get_str1(); // 编译错误,用非constexpr函数对constinit变量进行初始化
constinit const char *str3 = hahah; // 编译错误,用非常量表达式对constinit变量进行初始化
 
int main()
{
    static constinit const char *str4 = get_str2(); // 编译正确
    constinit const char *str5 = get_str2();// 编译错误,必须是静态 或 线程存储持续时间的变量
    constinit thread_local const char *str6; // 编译正确
    return 0;
}

2  语法

2.1  位域变量的默认成员初始化

位域变量在声明时可进行初始化。

位域变量的声明语法格式:

  • 标识符 变量名 : 位数
  • 标识符 变量名 : 常量表达式、大括号

例子:

int a;
const int b = 1;
 
struct S
{
    int x1 : 8 = 42;   // 正确,x1为8位的变量,并且初始化为42,“=42”为常量表达式
    int x2 : 6 {42};   // 正确,x2为6位的变量,并且初始化为42
    int x3 : true ? 10 : a = 20; // 正确,x3为10位变量,不进行初始化,赋值号优先于三目运算符
    int x4 : true ? 10 : b = 20; // 错误,b为const变量,不可赋值
    int x5 : (true ? 10 : b) = 20; // 正确,x5为10位的变量,并且初始化为20
    int x6 : false ? 10 : a = 20; // 错误,a = 10不是常量表达式
};

2.2  修改const限定的成员指针

在一个右值的 .* 表达式中,如果表达式的第二个参数是指向以&修饰的成员函数的指针,那么这个程序就是不规范的,除非限定符是const

例子:

struct S { void foo() const& { } };
 
void f()
{
    S{}.foo(); // 正确,没问题
    (S{}.*&S::foo)(); // C++20起支持该语法
}

2.3  允许lambda表达值按值捕获this

例子:

struct S
{
    int value;
    void print()
    {
        auto f = [=, this]() {
            this->value++;
        };
    }
}

2.4  指定初始化

在构造对象时,可以指定成员进行初始化,但是初始化的顺序必须与成员的内存顺序一致。

例子:

struct A { int x, y; };
struct B { int y, x; };
 
 
void f(A a, int); // #1
void f(B b,); // #2
void g(A a); // #3
void g(B b); // #4
 
void h()
{
    f({.x = 1, .y = 2}, 0); // 正确,调用#1
    f({.y = 1, .x = 2}, 0); // 错误,调用#1,初始化顺序不匹配
    f({.y = 1, .x = 2}, 1, 2, 3); // 正确,调用#2
    g({.x = 1, .y = 2}); // 错误,无法确定调用#3还是#4
}
 

2.5  lambda表达式支持模板

从新版开始,lambda表达式支持模板编程,且支持自动推导。(官方的说明是:支持未鉴定的上下文)

例子1:

int a;
 
auto f = [&a]<typename T>(const T &m) {
    a += m;
};
 
f(10);

例子2:

template<typename T>
int func(int t) 
{
    return t * t;
}
 
int f()
{
    return func<decltype([] {})>(20);
}

例子3:

using A = decltype([] {});
void func(A *) { }
 
func(nullptr);
 
 
template<typename T>
using B = decltype([] {});
 
void f1(B<int> *) { }
 
template<typename T>
void f2(B<T> *) { }
 
f1(nullptr);
f2<int>(nullptr); 

2.6  从构造函数推导出模板参数类型

声明变量时进行初始化,如果能从构造函数中推导出变量类型,则该变量的类型可以不用指定模板参数。

例子:

vector v{vector{1, 2}}; // 正确,v 推导为vector<vector<int>>类型
tuple t{tuple{1, 2}}; //正确,t 推导为tuple<int, int>类型

2.7  基于范围的for循环初始化

直接上例子:

2.8  简化lambda的隐式捕获

本人水平有限,暂时不能展示。

2.9  ADL与不可见的模板函数

ADL是C++本来就有的机制,用于自动推断调用的函数的位置,从而简化代码的编写。而新特性扩展了ADL机制,可以用于模板函数的推断。

例子:

int h;
void g();
 
namespace N
{
    struct A {};
    template<typename T> int f(T);
    template<typename T> int g(T);
    template<typename T> int h(T);
}
 
int x = f<N::A>(N::A()); // 正确,调用N::f
int y = g<N::A>(N::A()); // 正确,调用N::g
int z = h<N::A>(N::A()); // 错误,h是变量,不是模板

2.10  operator<=>

因为篇幅过长就不再在这里详细赘述了,感兴趣的可以自行查看http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r3.pdf。因此,我在这里只简单说一下。

来了解一下不同类型的比较策略(专有名词就不翻译了),并且可进行向下进行相对应的隐式转换:

| 策略 | 数值型结果 | 非数值型结果 |
| -1 | 0 | 1 |
| strong_ordering | less | equal | greater | unordered |
| weak_ordering | less | equivalent | greater |
| partial_ordering | less | equivalent | greater |
| strong_equality | unequal | equal | unequal |
| weak_equality | nonequivalent | equivalent | nonequivalent |

至于什么时候用到哪一种策略,这里有一位博主翻译好了的https://blog.csdn.net/qq_31359295/article/details/78799020,这里不再讲解(懒)。

2.11  基于范围的for循环初始化

新增的for循环语法格式:

for([init-statement;] for-range-declaration : for-range-initializer) …

例子:

    int a[] = {1, 2, 3, 4};
    for(int b = 0; int i : a) 
    {
        ...
    }

2.12  默认可构造可分配的无状态lambdas

简单点说,就是可以获取lambda或函数对象的类型,并且还可以创建对象。

举个例子感受一下:

#include <iostream>
#include <map>
 
auto greater = [](auto x, auto y) { return x > y; };
std::map<std::string, int, decltype(greater)> map;
 
static void f() 
{}
 
int main() 
{
    decltype(f) ff;
    ff();
 
    decltype(greater) d;
    d(10, 20);
    return 0;
}

2.13  专门的访问检查

我能力有限,不能准确理解文档的意思。这个特性在GCC、MSVC编译器中早已实现,但在其他的编译器以前的版本中并未实现。

我的理解是,在模板类内,可以忽略访问权限而访问到其他类内的嵌套类。

例子:

class A
{
    struct impl1
    { int value; };
 
    template<typename T>
    class impl2
    { T value; };
 
    class impl3
    { int value; };
};
 
struct B
{
    A::impl1 t; // error: 'struct A::impl1' is private within this context
};
 
template<typename T>
struct trait
{
    A::impl1 t;     // ok
    A::impl2<T> t2; // ok
 
    void func()
    {
        A::impl1 tmp;  // ok
        tmp.value = 10;// ok
        t2.value = 20; // ok
 
        A::impl3 t3;   // ok
        t3.value = 30; // ok
    }
};
 
int main() 
{
    trait<int> a;
    a.t.value = 10; // ok
    a.t2.value = 20; // error: 'int A::impl2<int>::value' is private within this context
    return 0;
}

2.14  constexpr函数的实例化

当仅仅获取constexpr函数的返回值类型时,不对函数进行实例化,即仅推导返回值类型,而不对函数进行调用。

template<typename T> 
constexpr int f() 
{ return T::value; }
 
// 此处仅仅推导f<T>()的返回值类型
template<bool B, typename T> 
void g(decltype(B ? f<T>() : 0)) { }
 
template<bool B, typename T> void g(...) { }
 
// 因为需要获取int类型的数据,所以需要执行f<T>()函数
template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})) { }
 
template<bool B, typename T> void h(...) { }
 
void x() 
{
    g<false, int>(0); // OK, B ? f<T>() : 0 is not potentially constant evaluated
    h<false, int>(0); // error, instantiates f<int> even though B evaluates to false and
                      // list-initialization of int from int cannot be narrowing
}

2.15  允许lambda在初始化捕获时进行包扩展

扩展了包扩展的应用范围

例子:

#include <functional>
 
template<class F, class... Args>
auto invoke1(F f, Args... args) 
{
    // 这种写法的效果跟[=]一致
    return [f, args...]() -> decltype(auto) 
    {
        return std::invoke(f, args...);
    };
}
 
template<class F, class... Args>
auto invoke2(F f, Args... args) 
{
    // 注:三个点号写在参数前面
    return [f=std::move(f), ...args=std::move(args)]() -> decltype(auto) 
    {
        return std::invoke(f, args...);
    };
}
 
template<class F, class... Args>
auto invoke3(F f, Args... args) 
{
    // 在初始化捕获中构造元组
    return [f=std::move(f), tup=std::make_tuple(std::move(args)...)]() -> decltype(auto) 
    {
        return std::apply(f, tup);
    };
}

2.16  放宽结构化绑定,新增自定义查找规则

这个特性比较地牛逼了,以前的结构化绑定的限制比较多,现在放宽了限制,并且可以自定义绑定的第几个是哪个类型,而且可以指定解绑的个数。

自定义的条件:

①在类外实现get(Type)函数、或在类内实现Type::get()成员函数;

②在std命名空间内特化tuple_size和tuple_element结构体;

③get()的返回路径数量必须与tuple_size指定的数值相等,tuple_element特化的索引数量(且必须从0开始)必须与tuple_size指定的数值相等;

④get()函数中N的值对应的返回类型必须与tuple_element对应索引指定的类型相同。

例子1:

#include <string>
#include <tuple>
 
struct A
{
    int a;
    int b;
};
 
struct X : private A
{
    std::string value1;
    std::string value2;
};
 
// 第一种方式,类外实现get<>()
template<int N> 
auto& get(X &x) 
{
    if constexpr (N == 0) 
        return x.value2;
}
 
namespace std 
{
    // 指定结构化绑定数量为1个
    template<> 
    class tuple_size<X>
     : public std::integral_constant<int, 1> 
    {};
 
    // 指定结构化绑定的第一种类型为string
    template<> 
    class tuple_element<0, X> 
    {
    public: 
        using type = std::string;
    };
}
 
int main()
{
    X x;
    auto& [y] = x;// y的类型为string
 
    auto& [y1, y2] = x; // error: 2 names provided for structured binding, while 'X' decomposes into 1 element
 
    return 0;
}

例子2:

#include <string>
#include <tuple> // 必须包含tuple库
 
struct A
{
    int a;
    int b;
};
 
struct X : protected A
{
    std::string value1;
    std::string value2;
 
    // 第二种方式,在类内实现get<>
    template<int N> 
    auto& get() 
    {
        if constexpr (N == 0) 
            return value1;
        else if constexpr (N == 1)
            return a;
    }
 
};
 
namespace std 
{
    // 指定X类型结构化绑定的个数为2个
    template<> 
    class tuple_size<X>
     : public std::integral_constant<int, 2> 
    {};
 
    // 指定第一种类型为string类型
    template<> 
    class tuple_element<0, X> 
    {
    public: 
        using type = std::string;
    };
 
    // 指定第二种类型为int类型
    template<> 
    class tuple_element<1, X> 
    {
    public: 
        using type = int;
    };
 
}
 
int main()
{
    X x;
    auto& [y1, y2] = x; // y1为string类型,y2为int类型
 
    return 0;
}

2.17  放宽基于范围的for循环,新增自定义范围方法

以前的版本自定义类的for循环,需要实现begin()和end()的成员函数;新版本开始,可以不实现成员函数,而在类体外实现begin()和end(),具体看以下例子

例子:

#include <iostream>
 
struct X
{
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    int e = 5;
};
 
int* begin(X& x)
{
    return reinterpret_cast<int*>(&x);
}
 
int* end(X& x)
{
    return reinterpret_cast<int*>(&x) + sizeof(x) / sizeof(int);
}
 
int main()
{
    X x;
    for (int i : x) 
    {
        std::cout << i << std::endl;
    }
    
    std::cout << "finish" << std::endl;
    return 0;
}

2.18  类类型的非类型模板参数

比较拗口,放松了非类型模板参数的限制,可以用类类型作为模板的参数,但是条件是所需要的运算需要在编译期完成。

如下例:

#include <iostream>
 
struct A
{
    int value;  
    
    // 这里的constexpr是必须的
    constexpr bool operator==(const A &v) const
    { return value == v.value; }
};
 
template<A a, A b>
struct Equal
{
    static constexpr bool value = a == b;// 需要在编译期调用operator==
};
 
int main()
{
    static constexpr A a{10}, b{20}; // 作为模板的传入参数,也必须是常量
    std::cout << std::boolalpha << Equal<a, b>::value << std::endl; // 输出false
    std::cout << std::boolalpha << Equal<a, a>::value << std::endl; // 输出true
    return 0;
}

关于类类型的非类型模板参数的优化

①operator==的缺口

直接看例子,文字不好写

#include <iostream>
 
template<auto v>
int Value;
 
struct A
{
    int value;
};
 
int main()
{
    static constexpr A a{10}, b{20}, c{10};
 
    // 对于Value<a>和Value<b>,只要 (a<=>b) == 0,则&Value<a> == &Value<b>结果就true.
    // 关于 <=> 运算符可以往上面看
 
    std::cout << std::boolalpha << (&Value<a> == &Value<b>) << std::endl; // 输出false
    std::cout << std::boolalpha << (&Value<a> == &Value<c>) << std::endl; // 输出true
    return 0;
}

②模板参数的成员函数调用

因为模板参数是处于编译期计算的,因此,作为调用用于自定义类型的模板参数的成员函数时,这些成员必须是constexpr修饰的。

③类模板参数的相互推导

例子:

#include <string>
 
template<typename _Tp, std::size_t N>
struct MyArray
{
    constexpr MyArray(const _Tp (&foo)[N + 1])
    { std::copy_n(foo, N + 1, m_data); }
    
    auto operator<=>(const MyArray &, const MyArray &) = default;
    
    _Tp m_data[N];
};
 
template<typename _Tp, std::size_t N>
MyArray(const _Tp (&str)[N] -> MyArray<_Tp, N - 1>;
template<std::size_t N>
using CharArray = MyArray<char, N>;
// 在此例子中,用"hello"字符串去实例化A模板时,需要显式的提供size,这导致比较大的不便
template <std::size_t N, CharArray<N> Str>
struct A {};
using hello_A = A<5, "hello">;
// 既然这是编译期常量,那在编译期是可以计算出来的,因此C++20做了优化
template <CharArray Str>
struct B {};
using hello_B = B<"hello">;

④用户自定义字面量

引用上一个例子

template <CharArray Str>
auto operator"" _udl();
 
"hello"_udl; // 等价于operator""_udl<"hello">()

类类型的非类型模板参数的条件(满足任意一个):

①字面量

②是一个lvalue

③包含占位符的类型

④派生类类型的一个占位符

⑤拥有强结构可比较性,没有mutable或者volatile修饰的子对象,拥有声明为public且指定为default的operator<=>

关于强结构可比较性的定义:

对于任意一种类型T,const T的一个glvalue对象x,x<=>x是类型std::strong_ordering或者std::strong_equality的有效表达式,它既不调用三向比较操作符,也不调用结构比较运算符。

2.19  禁止使用用户自己声明的构造函数来进行聚合初始化

旧版的几个问题

①delete了构造函数,却依然可以实例化

struct X 
{
    X() = delete;
};
 
int main()
{
    X x1; // 错误,无参构造函数为delete
    X x2{}; // 编译通过了(问题一,实际上应该编译不通过才对)
    return 0;
}

②双重聚合初始化

struct X 
{
    int i{4};
    X() = default;
};
 
int main()
{
    X x1(3); // 错误,没有带int类型的构造函数
    X x2{3}; // 编译通过,(问题二,非静态数据成员的双重聚合初始化)
    return 0;
}

③类外指定构造函数default

struct X 
{
    int i;
    X() = default;
};
struct Y 
{
    int i;
    Y();
};
 
Y::Y() = default;
 
int main()
{
    X x{4}; // 正常,编译通过
    Y y{4}; // 编译不通过(问题三,Y结构被判定为非聚合结构)
    return 0;
}

解决方案

简化并统一初始化语义

如果用户显式声明了非移动和拷贝构造函数的其他构造函数,则类的对象必须通过其中一个构造函数进行初始化。

上面三个问题的修正结果:

struct X 
{
    X() = delete;
};
 
int main()
{
    X x1; // 编译错误,无参构造函数为delete
    X x2{}; // 编译错误,无参构造函数为delete
    return 0;
}

struct X 
{
    int i{4};
    X() = default;
};
 
int main()
{
    X x1(3); // 错误,没有X::X(int)构造函数
    X x2{3}; // 错误,没有X::X({...})构造函数
    return 0;
}

#include <initializer_list>
//--------------------//
struct X 
{
    int i;
    X() = default;
};
struct Y 
{
    int i;
    Y();
};
Y::Y() = default;
 
//--------------------//
struct A
{
    int i;
    A(int);
};
struct B
{
    int i;
    B(int);
};
B::B(int){};
 
struct C
{
    int i;
    C() = default;
    C(std::initializer_list<int> list);
};
 
int main()
{
    X x{4}; // 编译错误,没有X::X({...})构造函数
    Y y{4}; // 编译错误,没有X::X({...})构造函数
 
    A a{5}; // 编译通过
    B b{5}; // 编译通过
    C c{6}; // 编译通过
    return 0;
}

2.20  嵌套内联命名空间

简化内联命名空间的嵌套语法

旧例子:

#include <iostream>
 
namespace A
{
    inline namespace B
    {
        void func()
        {
            std::cout << "B::func()" << std::endl;
        }
    } // namespace B
} // namespace A
 
int main()
{
    A::func(); // 输出 B::func()
    return 0;
}

新特性例子:

#include <iostream>
 
namespace A
{
    namespace B
    {
        void func()
        {
            std::cout << "B::func()" << std::endl;
        }
    } // namespace B
} // namespace A
 
namespace A::inline C
{
    void func()
    {
        std::cout << "C::func()" << std::endl;
    }
} // namespace C
 
int main()
{
    A::func(); // 输出C::func()
    return 0;
}

2.21  约束声明的另一种办法

利用concept与auto的特性,增加了新的约束声明方法。

例子:

#include <iostream>
 
struct Compare
{
    // 无约束,用auto代替模板类型
    bool operator()(const auto &t1, const auto &t2) const
    { return t1 < t2; }
};
 
template<typename T>
concept CanCompare = requires(T t){
    t * t;  // T类型需要提供*运算符
    Compare().operator()(T(), T()); // 根据Compare结果体,需要T类型提供<运算符
};
 
// concept与auto的结合
CanCompare auto pow2(CanCompare auto x)
{
    CanCompare auto y = x * x;
    return y;
}
 
struct A
{
    int value = 0;
 
    bool operator<(const A &a) const
    { return value < a.value; }
 
    A operator*(const A &a) const
    { return {.value = a.value * this->value}; }
};
 
int main()
{
    A a;
    a.value = 100;
    A aa = pow2(a);// 推导参数x为A类型,A类型符合CanCompare约束,编译通过
    std::cout << aa.value << std::endl;
    return 0;
}

2.22  允许在常量表达式中使用dynamic_cast多台typeid

待续

2.23  允许用圆括弧的值进行聚合初始化

简单地说,就是相当于默认有一个有全部非静态数据成员的构造函数。前提条件:目标类型必须符合聚合初始化的条件。

例子:

#include <iostream>
 
struct A
{
    int v;
};
struct B
{
    int a;
    double b;
    A &&c;
    long long &&d;
};
 
A get() 
{
    return A();
}
 
int main()
{
    int i = 100;
    B b1{1, 20.0, A(), 200}; // 编译通过
    B b2(1, 20.0, A(), 300); // 编译通过
    B b3{1, 20.0, get(), 300}; // 编译通过
    B b4(2, 30.0, std::move(get()), std::move(i));// 编译通过
    return 0;
}

2.24  new表达式的数组元素个数的推导

从C++20起,new表达式支持数组元素个数的自动推导。

例子:

#include <iostream>
#include <cstring>
 
int main()
{
    double a[]{1,2,3}; // 普通的做法
    double *p = new double[]{1,2,3}; // 编译通过
    p = new double[0]{};  // 编译通过
    p = new double[]{}; // 编译通过
    char *d = new char[]{"Hello"}; // 编译通过
    int size = std::strlen(d);
    std::cout << size << std::endl; // 输出5
    return 0;
}

2.25  unicode字符串字面量

新增两种字面量,分别是utf-16和utf-32编码字符串字面量

例子:

#include <string>
 
int main()
{
    std::u16string str1 = u"aaaaaa"; // 小写u是utf-16字符串
    std::u32string str2 = U"bbbbbb"; // 大写U是utf-32字符串
    return 0;
}

2.26  允许转换成未知边界的数组

这个特性比较简单,在实参为数组的传参时形参可以是无边界的数组。

例子:

template<typename T>
static void func(T (&arr)[]) 
{
 
}
 
template<typename T>
static void func(T (&&arr)[]) 
{
    
}
 
int main()
{
    int a[3];
    int b[6];
    func<int>(a);
    func<int>(b);
    func<int>({1, 2, 3, 4});
    func<double>({1.0, 2, 3, 4, 8.0});
    return 0;
}

乍一看,好像很鸡肋的特性,不知道数组的长度,长度无法获取,数组的遍历不知道终点,暂时不清楚应用场景。

2.27  聚合初始化推导类模板参数

通过聚合初始化中的参数类型 来 推导出类模板参数类型

例子:

template <typename T>
struct S 
{
    T x;
    T y;
};
 
template <typename T>
struct C 
{
    S<T> s;
    T t;
};
 
template <typename T>
struct D 
{
    S<int> s;
    T t;
};
 
C c1 = {1, 2}; // error: deduction failed
C c2 = {1, 2, 3}; // error: deduction failed
C c3 = {{1u, 2u}, 3}; // OK, C<int> deduced
D d1 = {1, 2}; // error: deduction failed
D d2 = {1, 2, 3}; // OK, braces elided, D<int> deduced
 
template <typename T>
struct I 
{
    using type = T;
};
 
template <typename T>
struct E 
{
    typename I<T>::type i;
    T t;
};
 
E e1 = {1, 2}; // OK, E<int> deduced

2.28  隐式地将返回的本地变量转换为右值引用

在以下的复制操作中,将会隐式采用移动操作代替复制操作的情况:

①如果return或co_return中的表达式是一个id-expression,其是在函数的最内层语句块或lambda表达式的主体或者参数声明子句中声明的隐式可移动实体。

②throw表达式的一个隐式可移动实体id-expression,其范围不超出最内层try块  或  [复合语句或构造函数初始值包含该throw表达式的函数try块(如果有)] 的复合语句。

例子:

#include <iostream>
 
struct base {
    base() {}
    base(const base &)
    { std::cout << "base(const base &)" << std::endl; }
private:
    base(base &&)
    { std::cout << "base(base &&)" << std::endl; }
};
 
struct derived : base {};
 
base f() {
    base b;
    throw b; // move
    derived d;
    return d;
}
 
int main()
{
    try
    {
        f();
    }
    catch(base)
    { }
    return 0;
}

2.29  允许default修饰运算符按值比较

直接例子:

struct C
{
    // 参数为按值传递
    friend bool operator==(C, C) = default; // C++20起支持
};

2.30  非类型模板参数等效的条件

相同类型的两个值,模板参数等效的条件(之一):

①整型且值相同;

②浮点类型且值相同;

③是std::nullptr_t类型;

④枚举类型,且枚举值相同;

⑤指针类型,且指针值相同;

⑥指向成员的指针类型,且引用相同的类成员,或者都是空成员指针值;

⑦引用类型,且引用相同的对象或函数;

⑧数组类型,对应元素满足模板参数等效;

⑨共用体类型,或者都没有活动成员,或者都具有相同的活动成员,且活动成员都是满足模板参数等效;

⑩类类型,且对应的直接子对象和引用成员满足模板参数等效。

3  宏

无。

4  属性

4.1  likely和unlikely

该属性用于指示switch分支结构的优化,likely表示“很大可能”落到指定分支,而unlikely表示“很小概率”落到指定分支。

例子:

int f(int i) 
{
    switch(i) {
    case 1: [[fallthrough]];
    [[likely]] case 2: return 1;
    [[unlikely]] case 3: return 2;
    }
    return 4;
}

4.2  no_unique_address

这个属性比较的复杂,有以下特性:

①同类型的子对象或成员不占用同一个地址;

②当地址不够分配时,则按照一般做法扩展空间,继续为未分配地址的no_unique_address属性成员分配地址,直至全部分配完毕;

③该属性对空类型(没有非静态数据成员)有效。

例子1:

#include <iostream>
 
struct A
{ };  // 空类型
 
struct B
{
    long long v;
    [[no_unique_address]] C a, b;
};
 
int main()
{
    B b;
    std::cout << &b.v << std::endl; // 输出v地址
    std::cout << &b.a << std::endl; // a地址为 &v + 1
    std::cout << &b.b << std::endl; // b地址为 &v + 2
    std::cout << sizeof(B) << std::endl; // 输出 8
    return 0;
}

例子2:

#include <iostream>
 
struct A
{  }; // 空对象
 
struct B
{
    int v;
    [[no_unique_address]] A a, b, c, d, e, f, g;
};
 
int main()
{
    B b;
    std::cout << &b.v << std::endl; // 得到v地址
    std::cout << &b.a << std::endl; // a地址为 &v + 1
    std::cout << &b.b << std::endl; // a地址为 &v + 2
    std::cout << &b.c << std::endl; // a地址为 &v + 3
    std::cout << &b.d << std::endl; // a地址为 &v + 4
    std::cout << &b.e << std::endl; // a地址为 &v + 5
    std::cout << &b.g << std::endl; // a地址为 &v + 6
    std::cout << &b.f << std::endl; // a地址为 &v + 7
 
    // 由于空间不足,按照一般的内存对齐方式自动扩展空间
    std::cout << sizeof(B) << std::endl; // 输出 8
    return 0;
}

例子3:​​​​​​​

#include <iostream>
 
struct A
{ [[no_unique_address]] int value; };
 
struct B
{
    int v;
    [[no_unique_address]] A a, b, c;
};
 
int main()
{
    B b;
    std::cout << &b.v << std::endl; // 得到v地址
    std::cout << &b.a << std::endl; // a地址为 &v + 4
    std::cout << &b.b << std::endl; // a地址为 &v + 8
    std::cout << &b.c << std::endl; // a地址为 &v + 12
    std::cout << sizeof(B) << std::endl;// 输出16
    return 0;
}
 

4.3  nodiscard

新增可选信息

例子:

[[nodiscard("asdfasfa")]] 
const char * get()
{
    return "";
}
 
int main()
{
    get(); // warning: ignoring return value of 'const char* get()', declared with attribute 'nodiscard': 'asdfasfa' [-Wunused-result]
    return 0;
}

5  弃用

5.1  lambda弃用使用[=]来隐式捕获this

struct X 
{
    int x;
    void foo(int n) 
    {
        auto f = [=]() { x = n; };         // 弃用:此处的x是this->x,而非拷贝
        auto g = [=, this]() { x = n; };   // 新版推荐的方法
    }
};

5.2  比较运算符的改进

①弃用枚举的隐式算术转换

enum E1 { e };
enum E2 { f };
 
int main()
{
    bool b = e <= 3.7;    // deprecated
    int k = f - e;        // deprecated
    auto cmp = e <=> f;   // ill-formed
 
    return 0;
}

②数组的比较

int arr1[5];
int arr2[5];
bool same = arr1 == arr2;   // deprecated, 效果与&arr1[0] == &arr2[0]相同,并非比较数组内容
auto cmp = arr1 <=> arr2;   // ill-formed

5.3  弃用下标表达式中的逗号操作符

在下标访问时,弃用逗号分隔的多个参数的语法。

如例子:

int main()
{
    int a[3]{0, 1, 3};
    // 在如下的逗号操作符中,只保留最后一个有效,这个特性不变
    int tmp1 = a[4, 1]; // tmp1 = a[1] = 1
    int tmp2 = a[10, 1, 2]; // tmp2 = a[2] = 3
    return 0;
}

后记

关于C++20新特性的英文文档本人已提供免费下载,感兴趣的可以自行下载:https://download.csdn.net/download/qq811299838/12554178

我的内容只是展示应用层面,而不对新特性的目标进行阐述,因为这样子可以少写很多字。

另外,如有问题,欢迎指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/896034.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32CUBEIDE FreeRTOS操作教程(七):queue队列

STM32CUBEIDE FreeRTOS操作教程&#xff08;七&#xff09;&#xff1a;queue队列 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件&#xff0c;不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板为例&#xff…

家用wifi的ip地址固定吗?换wifi就是换ip地址吗

在探讨家用WiFi的IP地址是否固定&#xff0c;以及换WiFi是否就意味着换IP地址这两个问题时&#xff0c;我们首先需要明确几个关键概念&#xff1a;IP地址、家用WiFi网络、以及它们之间的相互作用。 一、家用WiFi的IP地址固定性 家用WiFi环境中的IP地址通常涉及两类&#xff1a…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第十四集:制作新的场景以及制作创建切换管理系统

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作新的场景 1.重新翻新各种Sprite2.制作地图前期应该做的事情3.疯狂的制作地图二、制作场景切换管理系统 1.制作场景切换点TransitionPoint2.切换场景时的…

【Linux内核】eBPF基础篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了学习ebpf机制的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于–知乎ebpf专栏文章–进行的&#xff0c;每个知识点的修正和深…

【JavaEE初阶】深入理解TCP协议中的封装分用以及UDP和TCP在网络编程的区别

前言 &#x1f31f;&#x1f31f;本期讲解关于TCP/UDP协议的原理理解~~~ &#x1f308;上期博客在这里&#xff1a;【JavaEE初阶】入门视角-网络原理的基础理论的了解-CSDN博客 &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; …

前端算法:时间复杂度和空间复杂度

一、算法的重要性 1.为什么前端开发需要学习算法&#xff1f; 学习算法可以帮助培养逻辑思维能力&#xff0c;在面对复杂的问题时&#xff0c;能够系统性地分析问题、分解步骤并成功找到的正确的解决方案。 掌握基本的排序、查找算法和时间复杂度分析可以帮助编写更高效的代码…

移动网络知识

一、3G网络 TD-SCDMA&#xff08;时分同步码分多址接入&#xff09;、WCDMA&#xff08;宽带码分多址&#xff09;和CDMA2000三种不同的3G移动通信标准 TD-SCDMA&#xff08;时分同步码分多址接入&#xff09;&#xff1a;中国自主开发的一种3G标准主要用于国内市场&#xff…

零跑“半价平替”杀疯了,没钱别硬上问界理想

文 | AUTO芯球 作者 | 雷慢 你绝对想不到&#xff0c; 现在造车新势力的周销量榜第二名已经是零跑了 来看啊&#xff0c;十月第2周&#xff0c; 零跑周销量8700量&#xff0c;已经超过问界的7100辆&#xff0c; 放以前&#xff0c;问界也是周销量9000台左右的主&#xff0…

RHCE——时间服务器

NTP——网络时间协议&#xff0c;通过udp123端口进行网络时钟同步 chronyd chronyd——一个开源自由的网络时间协议 NTP 的客户端和服务器软件。能让计算机保持系统时钟与时钟服务器&#xff08;NTP&#xff09;同步&#xff0c;从而使计算机保持精确的时间。 Chrony由两个程…

大数据查询引擎之Tez

Apache Tez 是一个用于大数据处理的分布式计算框架&#xff0c;旨在提高 Hadoop 的 MapReduce 计算引擎的效率和性能。它是一个面向 DAG&#xff08;有向无环图&#xff09;任务执行的框架&#xff0c;主要用于大规模数据处理场景中&#xff0c;特别是在 Apache Hadoop 生态系统…

开放式耳机好不好用?盘点开放式蓝牙耳机排行榜前五名

​开放式耳机是好用的&#xff0c;目前非常流行&#xff0c;它们以时尚、美观和舒适著称&#xff0c;迅速赢得了众多用户的喜爱&#xff0c;成为了耳机市场的新宠。与传统的入耳式耳机相比&#xff0c;开放式耳机佩戴更稳固&#xff0c;对耳朵也更为温和。尽管有些人认为它们价…

C++在vscode中的code runner配置/环境配置

C在vscode中快捷运行&#xff08;code runner&#xff09; 一、配置tasks.json 在vscode中创建文件夹或打开文件夹&#xff0c;会发现文件夹下多了一个.vscode文件夹&#xff0c;在该文件夹下创建tasks.json文件&#xff0c;并添加一下内容 {"version": "2.0…

单周期处理器设计思路

目录 单周期处理器设计思路加法器的优化行波进位加法器&#xff08;RCA&#xff09;先行进位加法器&#xff08;CLA&#xff09;两种加法器的对比CLA的再优化可以用加法器实现的其他操作 编写可维护的RTL代码 单周期处理器设计思路 加法器的优化 &#xff08;用综合器综合*/等…

如何修改MAC地址破解网络无线网络限制-担心别人蹭网,路由器设置MAC地址过滤,限定了能访问无线网络的网卡地址-供大家学习参考

路由器都设置了MAC地址过滤&#xff0c;也就是限定了能访问无线网络的网卡的MAC地址。因为无线路由器不一定由自己控制&#xff0c;所以当更换了笔记本或者更换了无线网卡的时候&#xff0c;也许就上不了网了。我们可以修改网卡的MAC地址实现上网。 下载&#xff1a;https://do…

R01 vue+springboot 高考志愿推荐AI问答大数据平台

可以查看本文系统对应的视频讲解&#xff1a; vuespringboot 高考推荐AI问答志愿推荐大数据 R01 带增删改查、大屏、支持爬虫 1 系统背景 近年来&#xff0c;高考作为中国教育体系中最重要的考试之一&#xff0c;承载了无数考生和家庭的梦想。随着信息技术的迅猛发展&#xff…

Linux shell脚本文件通过shc工具加密,生成静态链接可执行文件

要使用 shc 工具对 Linux shell 脚本进行加密并生成静态链接的可执行文件&#xff0c;你可以按照以下步骤操作&#xff1a; 安装 shc 工具&#xff1a; 如果你的系统中还没有安装 shc&#xff0c;可以通过包管理器安装&#xff0c;例如在 Ubuntu 系统中&#xff0c;可以使用以下…

YOLOv11模型改进-模块-引入空间池化模块StripPooling 解决遮挡、小目标

本篇文章将介绍一个新的改进机制——空间池化模块StripPooling&#xff0c;并阐述如何将其应用于YOLOv11中&#xff0c;显著提升模型性能。首先&#xff0c;我们将解析StripPooling的工作原理&#xff0c;SP模块通过条带池化在水平和垂直方向上捕捉长距离依赖关系&#xff0c;增…

如何在线查看近8年的建筑覆盖变化

我们在《谷歌发布建筑数据&#xff0c;高度误差达惊人的1.5米》一文中介绍了谷歌2.5D建筑数据用途、制作方法以及数据下载方式。 现在我们演示下如何在线查看近8年的建筑物覆盖、建筑物质心和建筑物高度的变化。 历史建筑覆盖在线查看 2.5D建筑演变数据集包含2016年至2023年…

碰一碰支付怎么推广的?小白也能看懂的教程来了!

作为支付宝和微信全面力推的一个项目&#xff0c;碰一碰支付的热度可谓是节节攀升&#xff0c;连带着与之相关的话题&#xff0c;如碰一碰支付是什么和碰一碰支付怎么推广的等也因此成为了人们关注的焦点。那么&#xff0c;本期&#xff0c;我们就来详细地解答一下这两个问题。…

2024台州赛CTFwp

备注&#xff1a; 解题过程中&#xff0c;关键步骤不可省略&#xff0c;不可含糊其辞、一笔带过。解题过程中如是自己编写的脚本&#xff0c;不可省略&#xff0c;不可截图&#xff08;代码字体可以调小&#xff1b;而如果代码太长&#xff0c;则贴关键代码函数&#xff09;。…