龙空技术网

c++20 语法与性能介绍 part 1

桑丘一码农 188

前言:

现时看官们对“c语言有多个重载函数实例与参数列表匹配fabs”都比较重视,各位老铁们都需要知道一些“c语言有多个重载函数实例与参数列表匹配fabs”的相关文章。那么小编同时在网络上收集了一些关于“c语言有多个重载函数实例与参数列表匹配fabs””的相关内容,希望大家能喜欢,我们快快来了解一下吧!

目 录

● 01、语言新特性及实例

● 02、性能相关

主要版本的典型内容

c++发展历程

• C++98。包括:模板、包含容器和算法的标准模板库 (STL)、字符串和IO流。

• C++11。被称为现代C++,它的许多特性从根本上改变 了C++的编程方式。例如, C++11引入了TR1组件,还 有移动语义、完美转发、可变模板或constexpr等特性; 此外,我们还获得了内存模型作为基础的线程模型以 及线程API。

• C++14。是一个小型C++标准,引入了读写锁、泛化 lambda和泛化constexpr函数。

• C++17。有两个突出的特性:并行的STL和标准化的文 件系统。并从boost中获得了文件系统以及3种新的数据 类型: std::optional、 std::variant和std::any。

• C++ 20。从根本上改变了编写C++的方式,程度不亚于 C++11,特别是以下四个重要特性: Ranges(范围库), Coroutines(协程), Concepts(概念)以及 Modules

(模块)。

• C++ 26。改进并发和并行性。神奇递归模板模式 (CRTP)

目前支持c++20的编译器

VS : 2 0 1 9 and above

gcc, clang, etc.

对于支持c++20语法的编译器,我们可以在这里查看: Compliers .

C + + 标 准 新 内 容1.1 模块

模块(cppm文件)自c++20开始引入,用于替换原来的头文件。用import代替include,如下所示。

person.cppm文件

person.cpp文件

模块用于解决头文件重复包含,次序混乱维护等相关的问题。模块可以分成子模块,要注意,一般子模块需要和上级模块一并导出给用户使用。

子模块(Submodules)

cppm文件

子模块的cpp文件:

datamodel.address.cpp

模块还支持分区(Module Partitions),不过分区用于在内部构建模块,并不会暴露给模块的用户,如下面的分区模块:

datamodel.person.cppm

不幸的是,当将实现文件与分区结合使用时,需要注意的一点是:只能有一个具有特定分区名的文件。因此,拥有一个以以下声明开头的实现文件是不正确的;相反,您可以直接将地址分区实现放在数据模型模块的实现文件中,如下所示:

分区不需要在模块接口分区文件中声明,它也可以在模块实现分区文件中声明,这是一个扩展名为.cpp的普通源代码文件,在这种情况下,它是一个实现分区,有时称为内部分区。此类分区不会由主模块接口文件导出。进一步假设数学函数的实现需要一些不能由模块导出的辅助函数。实现分区是放置这种辅助函数的完美位置。其他数学模块实现文件可以通过导入这个实现分区来访问这些辅助函数。例如,一个数学模块实现文件(math.cpp)可能是这样的:

1.2 string

原始字符串文字使,数据库查询,正则表达式,文件路径等变得容易:

string_viewsv{R"(this is anstring view example)"};

与数字之间的转换

转换成字符串: string to_string(Tval);

转自字符串:

The std::string_view Class

string_view基本上是const字符串&的临时替换,但没有开销。它从不复制字符串!

字符串格式化

C++20引入了std::format(),在<format>中定义,以格式化字符串。它基本上结合了C风格函数和C++ I/O流的所有优点。.

1.3 智能指针

unique_ptr适用于存储动态分配的旧c样式数组。std::shared_ptr是一个支持可以复制的共享所有权的智能指针。weak_ptr可以包含对由shared_ptr管理的资源的引用。weak_ptr并不拥有该资源,因此不会阻止shared_ptr释放该资源。当weak_ptr被销毁时(例如当它超出作用域时,weak_ptr)不会销毁指向资源;但是,它可以用来确定资源是否已被associ_x0002_ated shared_ptr释放。

1.4 属性(attribute) 和函数声明(Function declaration)

属性为实现定义的语言扩展提供了统一的标准语法,如GNU和IBM语言扩展 __attribute__((...)),微软扩展 __declspec(), etc.

函数声明将引入函数名称及其类型。函数定义将函数名称/类型与函数体关联起来。

(N)RVO-返回值优化当从一个函数返回一个局部变量或参数时,只需写返回对象;并且不要使用std::move()。

1.5 类

统一初始化,右值构造,右值赋值。

变量和对象初始化,都可以使用{}表达式。在03版本前数组的初始化也是可以使用{}表达式的,比如:

int arr[10] = {0};

现在,对象统一初始化,可以按类数据成员声明顺序来初始化他们。

静态数据成员可以在声明时,初始化。

测试用例

右值部分

default和delete的声明

使用delete,来替代private+不实现代码。

使用using 超/父类的声明。当然还有override声明,保证不是自己错误引入了新虚函数。

1.6 模板

Alias Templates、 Return Type of Function Templates、 VARIABLE TEMPLATESCONCEPTS、 VARIADIC TEMPLATES、 VARIADIC TEMPLATES、 METAPROGRAMMING.

使用using为类型或模板起个别名:

c++支持模板变量,还支持特化,如下所示

CONCEPTS

C++20引入了用于约束模板类型和类和函数模板的非类型参数的命名需求。这些谓词,在编译时进行计算,以验证传递给模板的模板参数。概念的主要目标是使与模板相关的编译器错误更具可读性。每个人都遇到过这样一种情况,即当您为类或函数模板提供错误的参数时,编译器会吐出数百行错误。要挖掘这些编译器错误以找到根本原因并不总是容易的。

如果不满足某些类型的约束,concepts允许编译器输出可读的错误消息。因此,为了获得有意义的语义错误,建议编写概念来建模语义需求。避免只对没有任何语义意义的语法方面进行验证的概念,例如只检查类型是否支持operator+的概念。这样的概念将只检查语法,而不检查语义。std::string字符串支持operator+,但显然,它对于整数与操作符+有完全不同的含义。另一方面,可排序和可交换等是concepts建模某些语义意义的很好的例子。它从熟悉的template<>说明符开始,但与类和函数模板不同,概念从不实例化。

约束表达式语法

再看看其他语言对模板参数的约束

c#模板约束

java模板约束

那些计算为布尔值的常量表达式可以直接用作概念定义的约束。它必须精确地计算为一个布尔值,而不需要任何类型转换。下面是一个示例:

template <typename T>concept C = sizeof(T) == 4;

require表达式的参数列表是可选的。每个requirement表达式都必须以一个分号结尾。有四种类型的requirements表达式:简单的(simple)、类型的(type)、复合的(compound)和嵌套的(nested)。

Simple Requirement

简单需求是一个任意的表达式语句,而不是从需求开头。不允许使用变量声明、循环、条件语句等。这个表达式语句从不被计算;编译器只是验证它的编译过程。例如,以下concept定义指定某个类型T必须具有增量性;也就是说,类型T必须支持后缀和前缀++运算符:

template <typename T>concept Incrementable = requires(T x) { x++; ++x; };

Type Requirements

类型要求会验证某个类型是否有效。例如,以下概念要求某个类型T具有value_type成员:

template <typename T>concept C = requires { typename T::value_type; };

类型需求还可以用于验证是否可以使用给定的类型来实例化某个模板。下面是一个示例:

template <typename T>concept C = requires { typename SomeTemplate<T>; };

Compound Requirements

Compound requirement可用于验证某物是否不会抛出任何异常和/或验证某种方法是否返回某种类型。其语法如下:

{ expression } noexcept -> type-constraint;

除了noexcept和->类型约束都是可选的。例如,下面的概念验证给定类型有一个标记为noexcept的成员函数swap()的方法:

template <typename T>concept C = requires (T x, T y) { { x.swap(y) } noexcept;};

类型约束(type-constraint)可以是任何所谓的类型约束。类型约束只是具有零个或多个模板类型参数的概念的名称。箭头(->)左侧的表达式类型将自动作为第一个模板类型参数传递给类型约束。因此,类型约束总是比相应概念定义的模板类型参数的数量少一个参数。例如,具有单一模板类型的概念定义的类型约束不需要任何模板参数;您可以指定空方括号、<>或省略它们。这听起来可能有点狡猾,但举一个例子就能明确地说明这一点。以下概念验证给定类型具有名为size()的方法,并且size函数的返回类型可转换为size_t类型:

template <typename T>concept C = requires (const T x) { { x.size() } -> convertible_to<size_t>;};

一个requires表达式可以有多个参数,并且可以包含一系列requirements表达式。例如,下面的概念要求T类型的实例具有可比性:

template <typename T>concept Comparable = requires(const T a, const T b) { { a == b } -> convertible_to<bool>; { a < b } -> convertible_to<bool>; // ... similar for the other comparison operators ...};

Nested Requirements

requires表达式可以具有嵌套的requirements。例如,这里有一个concept,它要求一个类型的大小为4个字节,以及支持前缀和后缀增量和递减操作的类型:

template <typename T>concept C = requires (T t) { requires sizeof(t) == 4; ++t; --t; t++; t--;};

Combining Concept Expressions

现有的concept表达式可以使用连词(&&)和分离(||)来组合。例如,假设您有一个可增量的概念,类似于可增量;下面的示例显示了一个要求类型既可增量又可递减的概念:

template <typename T>concept IncrementableAndDecrementable = Incrementable<T> && Decrementable<T>;

Type-Constrained auto

类型约束可用于约束用auto类型推导定义的变量,在使用函数返回类型推导时约束返回类型,可以约束缩写函数模板和通用lambda表达式中的参数,等等。例如,下面的编译得很好,因为类型被推导为int,它建模了可增量的概念:

Incrementable auto value1 { 1 };

Type Constraints and Function Templates

有几种不同的方法可以在函数模板中使用类型约束。首先是简单地使用熟悉的template<>语法,但不是使用类型名(或类),而是使用类型约束。下面是一个示例:

template <convertible_to<bool> T>void handle(const T& t);template <Incrementable T>void process(const T& t);

另一种语法是使用requires子句,如下所述:

template <typename T> requires constant_expressionvoid process(const T& t);

举例如下:

template <typename T> requires Incrementable<T>void process(const T& t);// 或者template <typename T> requires requires(T x) { x++; ++x; }void process(const T& t);

requires子句也可以在函数头之后指定,即所谓的尾随requires子句

template <typename T>void process(const T& t) requires Incrementable<T>;

Constraint Subsumption

我们可以使用不同类型的约束来重载函数模板。编译器将总是使用具有最具体约束的模板;更具体的约束会包含/意味着较小的约束。下面是一个示例:

template <typename T> requires integral<T>void process(const T& t) { cout << "integral<T>" << endl; }template <typename T> requires (integral<T> && sizeof(T) == 4)void process(const T& t) { cout << "integral<T> && sizeof(T) == 4" << endl; }//process(int { 1 }); // integral<T> && sizeof(T) == 4process(short { 2 }); // integral<T>

Type Constraints and Class Templates

到目前为止,所有类型约束示例都使用函数模板。类型约束也可以使用类似的语法。

template <std::derived_from<GamePiece> T>class GameBoard : public Grid<T>{ 	public: 		explicit GameBoard(size_t width = Grid<T>::DefaultWidth, 			size_t height = Grid<T>::DefaultHeight); 		void move(size_t xSrc, size_t ySrc, size_t xDest, size_t yDest);};// 或者使用下面的语法形式template <typename T> requires std::derived_from<T, GamePiece>class GameBoard : public Grid<T> { ... };

Type Constraints and Class Methods

还可以对类模板的特定方法设置额外的约束。

template <std::derived_from<GamePiece> T>class GameBoard : public Grid<T>{ public: explicit GameBoard(size_t width = Grid<T>::DefaultWidth, size_t height = Grid<T>::DefaultHeight); void move(size_t xSrc, size_t ySrc, size_t xDest, size_t yDest) requires std::movable<T>;};template <std::derived_from<GamePiece> T>void GameBoard<T>::move(size_t xSrc, size_t ySrc, size_t xDest, size_t yDest) requires std::movable<T>{ ... }

Type Constraints and Template Specialization

template <typename T>size_t Find(const T& value, const T* arr, size_t size){ for (size_t i { 0 }; i < size; i++) { if (arr[i] == value) { return i; // Found it; return the index. } } return NOT_FOUND; // Failed to find it; return NOT_FOUND.}

这个实现使用==操作符来比较值。通常不建议使用==来比较浮点类型,而是使用所谓的epsilon测试。下面的浮点类型的Find()特化使用了一个用AreEqual()助手函数实现的epsilon测试,而不是运算符==:

template <std::floating_point T>size_t Find(const T& value, const T* arr, size_t size){ for (size_t i { 0 }; i < size; i++) { 	if (AreEqual(arr[i], value)) { 		return i; // Found it; return the index. 	} } return NOT_FOUND; // Failed to find it; return NOT_FOUND.}template <std::floating_point T>bool AreEqual(T x, T y, int precision = 2){ // Scale the machine epsilon to the magnitude of the given values and multiply // by the required precision. return fabs(x - y) <= numeric_limits<T>::epsilon() * fabs(x + y) * precision || fabs(x - y) < numeric_limits<T>::min(); // The result is subnormal.}

VARIADIC TEMPLATES

可变模板可以采用可变数量的模板参数。例如,下面的代码定义了一个可以使用称为类型的参数包接受任意数量的模板参数:

template <typename... Types>class MyVariadicTemplate { };

需要注意的是:类型名后面的三个点都不是错误的。这是为可变模板定义参数包的语法。参数包可以接受可变数量的参数。可以在这三个点的前后放置空格。

当然,如果不允许实例化具有零模板参数的可变模板,可以按照如下方式编写模板:

template <typename T1, typename... Types>class MyVariadicTemplate { };

下面的例子展示了,如何使用模板递归,遍历变参模板参数。

void handleValue(int value) { cout << "Integer: " << value << endl; }void handleValue(double value) { cout << "Double: " << value << endl; }void handleValue(string_view value) { cout << "String: " << value << endl; }void processValues() // Base case to stop recursion{ /* Nothing to do in this base case. */ }template <typename T1, typename... Tn>void processValues(T1 arg1, Tn... args){   handleValue(arg1);   processValues(args...);}// processValues(1, 2, 3.56, "test", 1.1f);

不过,上面的例子有个问题,就是参数全部是拷贝的,为了提升性能,我们可以改成如下的代码:

void processValues() // Base case to stop recursion{ /* Nothing to do in this base case.*/ }template <typename T1, typename... Tn>void processValues(T1&& arg1, Tn&&... args){   handleValue(forward<T1>(arg1));   processValues(forward<Tn>(args)...);}

...操作符,用于解开参数包。使用std::forward()保留参数包中的每个参数的引用类型,并使用逗号隔离他们。

当T&&被用作函数或方法模板的参数时,或者当T作为其模板参数之一时,T&&只是一个转发引用。如果一个类方法有一个T&&参数,但T是一个类的模板参数,而不是方法本身的模板参数,那么这个T&&就不是一个转发引用,而只是一个右值引用。这是因为在编译器开始使用T&&参数处理该方法时,类模板参数T已经被解析为一个具体的类型,例如int,此时,该方法参数已经被int&&替换。

Variable parameter templates are used for inheritance

变参模板可以用于几乎所有的场合,比如,下面的代码,多重继承:

class Mixin1{ public: 	Mixin1(int i) : m_value { i } {} 	virtual void mixin1Func() { cout << "Mixin1: " << m_value << endl; } private: 	int m_value;};class Mixin2{ public: 	Mixin2(int i) : m_value { i } {} 	virtual void mixin2Func() { cout << "Mixin2: " << m_value << endl; } private: 	int m_value;};template <typename... Mixins>class MyClass : public Mixins...{ public: 	MyClass(const Mixins&... mixins) : Mixins { mixins }... {} 	virtual ~MyClass() = default;};// MyClass<Mixin1, Mixin2> a { Mixin1 { 11 }, Mixin2 { 22 } };a.mixin1Func();a.mixin2Func();MyClass<Mixin1> b { Mixin1 { 33 } };b.mixin1Func();//b.mixin2Func(); // Error: does not compile.MyClass<> c;//c.mixin1Func(); // Error: does not compile.//c.mixin2Func(); // Error: does not compile.

Fold Expressions

C-++支持所谓的折叠表达式。这使得在可变模板中使用参数包更加容易。下表列出了受支持的四种折叠类型。在此表中,Ѳ可以是以下任意一个操作符: + - * / % ^ & | << >> += -= *= /= %= ^= &= |= <<= >>= = == != < > <= >= && ||,.* ->*.

Fold Expressions

有了折叠表达式,可以避免使用递归方式定义模板:

template <typename... Tn>void processValues(const Tn&... args){	 (handleValue(args), ...);}

基本上,函数体内的三个点会触发折叠。该行被展开为为参数包中的每个参数调用handleValue(),并且handleValue()的每个调用用逗号分隔。实际展开为下面的形式:

(handleValue(a1), (handleValue(a2), handleValue(a3)));

在这些例子中,折叠与逗号运算符一起使用,但它几乎可以与任何类型的运算符一起使用。比如下面的求和算法:

template <typename T, typename... Values>double sumValues(const T& init, const Values&... values){ return (init + ... + values);}

会被展开为:

return (((init + v1) + v2) + v3);

上面的sumValues使用了二元运算符,也可以使用一元的左折叠表达式来实现:

template <typename... Values>double sumValues(const Values&... values) { return (... + values); }

上面的sumValue要求,在调用时,至少提供一个参数。不支持0个参数的调用,比如sumValues(); 则会编译报错了。

一元折叠允许零长度的参数包,但只能与逻辑AND(&&)、逻辑OR(||)和逗号(,)运算符组合。例如:

template <typename... Values>double allTrue(const Values&... values) { return (... && values); }template <typename... Values>double anyTrue(const Values&... values) { return (... || values); }int main(){ 		cout << allTrue(1, 1, 0) << allTrue(1, 1) << allTrue() << endl; // 011 		cout << anyTrue(1, 1, 0) << anyTrue(0, 0) << anyTrue() << endl; // 100}

METAPROGRAMMING

模板元编程的目标是在编译时而不是在运行时执行一些计算。它基本上是一种建立在另一种编程语言之上的编程语言。我们需要意识到,计算发生在编译期,而在运行期,我们只需要直接使用结果。

递归

示例,阶乘:

template <unsigned char f>class Factorial{public: static const unsigned long long value { f * Factorial<f - 1>::value };};template <>class Factorial<0>{ public: static const unsigned long long value { 1 };};int main(){ cout << Factorial<6>::value << endl;}

在c++20,可以使用consteval,来代替元编程(模板递归实现)。

consteval unsigned long long factorial(unsigned char f){ if (f == 0) { return 1; } else { return f * factorial(f - 1); }}

Loop Unrolling

模板元编程的第二个例子是在编译时展开循环,而不是在运行时执行循环。请注意,循环展开,只能在真正需要时进行,例如在性能关键代码中。编译器通常足够聪明,可以展开可以为您展开的循环。

这个示例再次使用模板递归,因为它需要在编译时在循环中做一些事情。在每个递归上,Loop类模板用i-1实例化自己。当它达到0时,递归将停止。

template <int i>class Loop{ public: 		template <typename FuncType> 		static inline void run(FuncType func) { 				Loop<i - 1>::run(func); 				func(i); 		}};template <>class Loop<0>{ public: template <typename FuncType> static inline void run(FuncType /* func */) { }};void doWork(int i) { cout << "doWork(" << i << ")" << endl; }int main(){ Loop<3>::run(doWork);}

这段代码导致编译器展开循环并连续三次调用函数doWork()。程序输出如下:

doWork(1)doWork(2)doWork(3)

constexpr if

从c++17开始,constexpr if,可以实现编译期if表达式;如果constexpr if表达式的某个分支,没有命中,那么这个分支是不会被编译的。下面的例子,展示打印tuple的代码,没有使用模板递归实现。

template <typename TupleType, int n>void tuplePrintHelper(const TupleType& t) { 		if constexpr (n > 1) { 			tuplePrintHelper<TupleType, n - 1>(t); 		} 		cout << get<n - 1>(t) << endl;}template <typename T>void tuplePrint(const T& t){ 		tuplePrintHelper<T, tuple_size<T>::value>(t);}// 或者如下面的实现template <typename TupleType, int n = tuple_size<TupleType>::value>void tuplePrint(const TupleType& t) { 		if constexpr (n > 1) { 				tuplePrint<TupleType, n - 1>(t); 		} 		cout << get<n - 1>(t) << endl;}

Compile-Time Integer Sequence with Folding

C++支持编译时整数序列使用std::integer_sequence,在<utility>中定义。模板元编程的一个常见用例是生成一个索引的编译时序列,即一个类型为size_t的整数序列。为此,可以使用一个助手std::index_sequence。您可以使用std::index_sequence_for()生成与给定参数包长度相同的索引序列。元组打印可以使用可变模板、编译时索引序列和折叠表达式实现:

template <typename Tuple, size_t... Indices>void tuplePrintHelper(const Tuple& t, index_sequence<Indices...>){ ((cout << get<Indices>(t) << endl), ...);}template <typename... Args>void tuplePrint(const tuple<Args...>& t){ tuplePrintHelper(t, index_sequence_for<Args...>());}// 下面用例tuple t1 { 167, "Testing"s, false, 2.3 };tuplePrint(t1);//将被展开(((cout << get<0>(t) << endl), ((cout << get<1>(t) << endl), ((cout << get<2>(t) << endl), (cout << get<3>(t) << endl)))));

Type Traits

类型特征允许您在编译时根据类型做出决策。例如,您可以验证一个类型是否从另一种类型派生出来,是否可转换为另一种类型,是否为积分,等等。C++标准库带有大量的类型性状选择。所有与类型性状相关的功能都在<type_性状>中定义。类型特征被分为不同的类别。

type traits

1.7 标准库动向

在<forward_list>中定义的forward与列表类似,只是它是一个单链列表,而列表是一个双链列表。在<array>中定义的array与vector相似,只是它的大小是固定的;它的大小不能增大或缩小。Span允许您编写一个函数来处理向量、c样式数组和std::arrays。而且span的拷贝性能很好。

unordered-set/map.

emplacexxx.

标签: #c语言有多个重载函数实例与参数列表匹配fabs #有多个重载函数fabs实例与参数列表匹配是什么意思