explicit(bool) 是 C++20 引入的一个特性,称为 Conditionally explicit。 核心目的是简化泛型类型的实现,提高性能,减少编译时间。 举个简单的例子: void foo(std::string); foo("a"); // OK foo("a"sv); // Error 因为没有相应的稳式转换, std::string 对于 std::string_view 的构造是 explicit 的,所以编译失败。这是普通的 explicit 用法,若 foo() 的参数变为 wrapper<std::string>,这里的 wrapper 就是前面所说的泛型类型,此时依旧想保持 foo() 的调用行为,在 C++20 之前可以使用 SFINAE。 template<class T> struct wrapper { template<class U, std::enable_if_t<std::is_convertible_v<U, T>>* = nullptr> wrapper(U const& u) :… Continue Reading T230418 explicit(bool)

新年伊始,要说什么选题最合适,那无疑是 C++23 了。 去年只写过 Ranges 和几个小特性的提案,对于其他特性,就全放在此篇一览究竟。 23 是个小版本,主要在于「完善」二字,而非「新增」。因此,值得单独拿出来写篇文章的特性其实并不多,大多特性都是些琐碎小点,三言两语便可讲清。 本篇包含绝大多数 C++23 特性,难度三星就表示只会介绍基本用法,但有些特性的原理也会深入讲讲。 Deducing this(P0847) Deducing this 是 C++23 中最主要的特性之一。msvc 在去年三月份就已支持该特性,可以在 v19.32 之后的版本使用。 为什么我们需要这个特性? 大家知道,成员函数都有一个隐式对象参数,对于非静态成员函数,这个隐式对象参数就是 this 指针;而对于静态成员函数,这个隐式对象参数被定义为可以匹配任何参数,这仅仅是为了保证重载决议可以正常运行。 Deducing this 所做的事就是提供一种将非静态成员函数的「隐式对象参数」变为「显式对象参数」的方式。为何只针对非静态成员函数呢?因为静态成员函数并没有 this 指针,隐式对象参数并不能和 this 指针划等号,静态函数拥有隐式对象参数只是保证重载决议能够正常运行而已,这个参数没有其他用处。 于是,现在便有两种写法编写非静态成员函数。 struct S_implicit { void foo() {} }; struct S_explicit { void foo(this S_explicit&) {} }; 通过 Deducing… Continue Reading Overview of C++23 Features

import std; auto main() -> int { std::println("Hello world!"); } 随着 Modules 和 Formatting Library 顺利进入 C++20,标准库 Modules 和 Formatted output 进入 C++23 不过是水到渠成。这种先引入再扩展的标准发展方式在 C++ 中已不稀奇,这两个新特性自然也就成为 C++23 中主要的特性之二。 C++2a 之后,Ranges, Modules, Concepts, Coroutines, 以及将要引入的新特性,都将会在一定程度上改变我们以往的编译方式。而这一切的基础,都在于新特性是否足够完善。三年一小步,六年一大步,此之谓也。 由经典的入门代码,可以看到,C++俨然成为了一门新语言。越现代,越强大,这种使用方式以后可能会越发流行。因为不论是性能还是可用性,新特性都更具有优势。 首先是标准库 Modules。 Modules 具有缩短编译时间、解决重复替换、导入顺序无关,以及无需分离接口与实现等等优势,若标准库迟迟不提供 Modules 版本,反而会阻碍 Modules 的发展。因此,标准库Modules的优先级并不低,进标准也是顺理成章。 其次是Formatted output。 用过 fmt 库的都清楚,这可是个好东西,或者可以说是 cout 终结者。… Continue Reading “Hello World” in C++23

设计程序,经常需要分离不变的和变化的逻辑。将不变的逻辑放到一块,再以某种形式为变化的部分提供「定制点」,从而使程序具有更好的可扩展性,同时增加相似逻辑的可复用性。 因此,本质上来说,设计是为了应对变化。通过抽离系统的变化点,再以合适的结构来表示变化,从而控制变化的影响范围。 C++ 提供许多技术来表示定制点,大家最熟悉的就是 OOP 的继承和多态,而 Concepts 也是其中之一。那么 Concepts 为何可以表示定制点?它又产生了怎样的结果?这种方式有哪些好处?又有哪些缺点?与继承的方式有哪些区别?流程有何变化?实际例子有哪些? 这些问题,就是本篇将要讨论的内容。 首先,让我们来探讨一个问题:变化的最小单元是什么? 一个程序是一个系统,一个系统必须具备三个最基本的元素:输入、处理和输出。这三个元素都存在变化,不同的输入对应不同的处理,导出不同的结果。在编程语言中,输入和输出对应「数据」,处理对应「方法」。不同类型的数据需要不同的方法来进行处理,无数个数据和方法构成了整个程序。因此,变化就存在于数据与方法当中。 OOP 通过组块,将数据与方法组合起来,称为一个类,一个类就是一些具有联系的数据与方法的集合。但是,只有一个类无法应对变化,任何变化都会引起对该类的修改,或是重新编写相同的方法。因此,对需要共用某些相同数据或方法的一些类,进行向上一层的抽象,把这些相似的数据或方法放到更高层级,将其他变化的逻辑放到更低层级,使得低层级类可以使用高层级类的共有逻辑,从而提供扩展和复用的能力。通过这种结构化方式,将具备相似关系的类置于一个「层级体系」,高层级的类具有更高的抽象,低层级的类则更加具体。于是,高层级的类可以用来表示接口,低层级的类可以用来定制具体实现,以此来表示变化。「继承」便是用来表示层级关系,而「多态」则是用来重新定义方法。 Concepts是命名的约束,约束其实是一种条件关系,只有满足某些条件才能触发接下来的操作。OOP中,处于同一层级体系的类本身就是一种约束,对于其他类,由于不在该层级当中,因此不符合约束,也就无法使用那些共有的操作了。若要使用这些共有方法,则需要通过继承添加定制点。既然Concepts表示约束,那么就也可以作为一种表示定制点的方式。 那么具体来看,继承和多态是如何表示定制点的呢? 以一个简单的例子来说明: // Dynamic Polymorphism struct Graph { virtual ~Graph() = default; virtual void draw() const = 0; }; struct Circle : Graph { float radius; void draw() const override { std::cout << "draw… Continue Reading 使用Concepts表示变化「定制点」