当前位置: 首页 > news >正文

C++20 小语法

这个提案允许在static_assert和if constexpr中从整形转换为布尔类型。
以下表格就可以表示所有内容。

对于严格的C++ 编译器来说,以前在这种情境下int无法向下转换为bool,需要手动强制转换,
C++23 这一情况得到了改善。 

对于严格的C++编译器来说,以前在这种情境下int无法向下转换为bool,需要手动强制转换,C++23这一情况得到了改善。

目前在GCC 9和Clang 13以上版本支持该特性。

11 forward_like(P2445)

这个在Deducing this那节已经使用过了,是同一个作者。

使用情境让我们回顾一下这个例子:

auto callback = [m = get_message(), &scheduler](this auto&& self) -> bool {return scheduler.submit(std::forward_like<decltype(self)>(m));
};callback();            // retry(callback)
std::move(callback)(); // try-or-fail(rvalue)

std::forward_like加入到了<utility>中,就是根据模板参数的值类别来转发参数。

如果closure type为左值,那么m将转发为左值;如果为右值,将转发为右值。

听说Clang 16和MSVC v19.34支持该特性,但都尚未发布。

12 #elifdef and #elifndef(P2334)

这两个预处理指令来自WG14(C的工作组),加入到了C23。C++为了兼容C,也将它们加入到了C++23。

也是一个完善工作。

#ifdef#ifndef分别是#if defined()#if !defined()的简写,而#elif defined()#elif !defined()却并没有与之对应的简写指令。因此,C23使用#elifdef#elifndef来补充这一遗漏。

总之,是两个非常简单的小特性。目前已在GCC 12和Clang 13得到支持。

13 #warning(P2437)

#warning是主流编译器都会支持的一个特性,最终倒逼C23和C++23也加入了进来。

这个小特性可以用来产生警告信息,与#error不同,它并不会停止翻译。

用法很简单:

#ifndef FOO
#warning "FOO defined, performance might be limited"
#endif

目前MSVC不支持该特性,其他主流编译器都支持。

14 constexpr std::unique_ptr(P2273R3)

std::unique_ptr也支持编译期计算了,一个小例子:

constexpr auto fun() {auto p = std::make_unique<int>(4);return *p;
}int main() {constexpr auto i = fun();static_assert(4 == i);
}

目前GCC 12和MSVC v19.33支持该特性。

15 Improving string and string_view(P1679R3, P2166R1, P1989R2, P1072R10, P2251R1)

stringstring_view也获得了一些增强,这里简单地说下。

P1679为二者增加了一个contain()函数,小例子:

std::string str("dummy text");
if (str.contains("dummy")) {// do something
}

目前GCC 11,Clang 12,MSVC v19.30支持该特性。

P2166使得它们从nullptr构建不再产生UB,而是直接编译失败。

std::string s { nullptr };       // error!
std::string_view sv { nullptr }; // error!

目前GCC 12,Clang 13,MSVC v19.30支持该特性。

P1989是针对std::string_view的,一个小例子搞定:

int main() {std::vector v { 'a', 'b', 'c' };// Beforestd::string_view sv(v.begin(), v.end());// Afterstd::string_view sv23 { v };
}

以前无法直接从Ranges构建std::string_view,而现在支持这种方式。

该特性在GCC 11,Clang 14,MSVC v19.30已经支持。

P1072为string新增了一个成员函数:

template< class Operation >
constexpr void resize_and_overwrite( size_type count, Operation op );

可以通过提案中的一个示例来理解:

int main() {std::string s { "Food: " };s.resize_and_overwrite(10, [](char* buf, int n) {return std::find(buf, buf + n, ':') - buf;});std::cout << std::quoted(s) << '\n'; // "Food"
}

主要是两个操作:改变大小和覆盖内容。第1个参数是新的大小,第2个参数是一个op,用于设置新的内容。

然后的逻辑是:

  • 如果maxsize <= s.size(),删除最后的size()-maxsize个元素;
  • 如果maxsize > s.size(),追加maxsize-size()个默认元素;
  • 调用erase(begin() + op(data(), maxsize), end())

这里再给出一个例子,可以使用上面的逻辑来走一遍,以更清晰地理解该函数。

constexpr std::string_view fruits[] {"apple", "banana", "coconut", "date", "elderberry"};
std::string s1 { "Food: " };s1.resize_and_overwrite(16, [sz = s1.size()](char* buf, std::size_t buf_size) {const auto to_copy = std::min(buf_size - sz, fruits[1].size()); // 6std::memcpy(buf + sz, fruits[1].data(), to_copy); // append "banana" to s1.return sz + to_copy; // 6 + 6
});std::cout << s1; // Food: banana

注意一下,maxsize是最大的可能大小,而op返回才是实际大小,因此逻辑的最后才有一个erase()操作,用于删除多余的大小。

这个特性在GCC 12,Clang 14,MSVC v19.31已经实现。

接着来看P2251,它更新了std::spanstd::string_view的约束,从C++23开始,它们必须满足TriviallyCopyable Concept。

主流编译器都支持该特性。

最后来看P0448,其引入了一个新的头文件<spanstream>

大家都知道,stringstream现在被广泛使用,可以将数据存储到stringvector当中,但这些容器当数据增长时会发生「挪窝」的行为,若是不想产生这个开销呢?

<spanstream>提供了一种选择,你可以指定固定大小的buffer,它不会重新分配内存,但要小心数据超出buffer大小,此时内存的所有权在程序员这边。

一个小例子:

#define ASSERT_EQUAL(a, b) assert(a == b)
#define ASSERT(a) assert(a)int main() {char input[] = "10 20 30";std::ispanstream is{ std::span<char>{input} };int i;is >> i;ASSERT_EQUAL(10,i);is >> i;ASSERT_EQUAL(20,i);is >> i;ASSERT_EQUAL(30,i);is >> i;ASSERT(!is);
}

目前GCC 12和MSVC v19.31已支持该特性。

16 static operator()(P1169R4)

因为函数对象,Lambdas使用得越来越多,经常作为标准库的定制点使用。这种函数对象只有一个operator (),如果允许声明为static,则可以提高性能。

至于原理,大家可以回顾一下Deducing this那节的Pass this by value提高性能的原理。明白静态函数和非静态函数在重载决议中的区别,大概就能明白这点。

顺便一提,由于mutidimensional operator[]如今已经可以达到和operator()一样的效果,它也可以作为一种新的函数语法,你完全可以这样调用foo[],只是不太直观。因此,P2589也提议了static operator[]

17 std::unreachable(P0627R6)

当我们知道某个位置是不可能执行到,而编译器不知道时,使用std::unreachalbe可以告诉编译器,从而避免没必要的运行期检查。

一个简单的例子:

void foo(int a) {switch (a) {case 1:// do somethingbreak;case 2:// do somethingbreak;default:std::unreachable();}
}bool is_valid(int a) {return a == 1 || a == 2;
}int main() {int a = 0;while (!is_valid(a))std::cin >> a;foo(a);
}

该特性位于<utility>,在GCC 12,Clang 15和MSVC v19.32已经支持。

18 std::to_underlying(P1682R3)

同样位于<utility>,用于枚举到其潜在的类型,相当于以下代码的语法糖:

static_cast<std::underlying_type_t<Enum>>(e);

一个简单的例子就能看懂:

void print_day(int a) {fmt::print("{}\n", a);
}enum class Day : std::uint8_t {Monday = 1,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
};int main() {// Beforeprint_day(static_cast<std::underlying_type_t<Day>>(Day::Monday));// C++23print_day(std::to_underlying(Day::Friday));
}

的确很简单吧!

该特性目前在GCC 11,Clang 13,MSVC v19.30已经实现。

19 std::byteswap(P1272R4)

位于<bit>,顾名思义,是关于位操作的。

同样,一个例子看懂:

template <std::integral T>
void print_hex(T v)
{for (std::size_t i = 0; i < sizeof(T); ++i, v >>= 8){fmt::print("{:02X} ", static_cast<unsigned>(T(0xFF) & v));}   std::cout << '\n';}int main()
{unsigned char a = 0xBA;print_hex(a);                     // BAprint_hex(std::byteswap(a));      // BAunsigned short b = 0xBAAD;print_hex(b);                     // AD BAprint_hex(std::byteswap(b));      // BA ADint c = 0xBAADF00D;print_hex(c);                     // 0D F0 AD BAprint_hex(std::byteswap(c));      // BA AD F0 0Dlong long d = 0xBAADF00DBAADC0FE;print_hex(d);                     // FE C0 AD BA 0D F0 AD BAprint_hex(std::byteswap(d));      // BA AD F0 0D BA AD C0 FE
}

可以看到,其作用是逆转整型的字节序。当需要在两个不同的系统传输数据,它们使用不同的字节序时(大端小端),这个工具就会很有用。

该特性目前在GCC 12,Clang 14和MSVC v19.31已经支持。

20 std::stacktrace(P0881R7, P2301R1)

位于<stacktrace>,可以让我们捕获调用栈的信息,从而知道哪个函数调用了当前函数,哪个调用引发了异常,以更好地定位错误。

一个小例子:

void foo() {auto trace = std::stacktrace::current();std::cout << std::to_string(trace) << '\n';
}int main() {foo();
}

输出如下。

0# foo() at /app/example.cpp:5
1#      at /app/example.cpp:10
2#      at :0
3#      at :0
4# 

注意,目前GCC 12.1和MSVC v19.34支持该特性,GCC 编译时要加上-lstdc++_libbacktrace参数。

std::stacktracestd::basic_stacktrace使用默认分配器时的别名,定义为:

using stacktrace = std::basic_stacktrace<std::allocator<std::stacktrace_entry>>;

而P2301,则是为其添加了PMR版本的别名,定义为:

namespace pmr {
using stacktrace =std::basic_stacktrace<std::pmr::polymorphic_allocator<std::stacktrace_entry>>;
}

于是使用起来就会方便一些。

// Before
char buffer[1024];std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};std::basic_stacktrace<std::pmr::polymorphic_allocator<std::stacktrace_entry>>trace{&pool};// After
char buffer[1024];std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};std::pmr::stacktrace trace{&pool};

这个特性到时再单独写篇文章,在此不细论。

21 Attributes(P1774R8, P2173R1, P2156R1)

Attributes在C++23也有一些改变。

首先,P1774新增了一个Attribute [[assume]],其实在很多编译器早已存在相应的特性,例如__assume()(MSVC, ICC),__builtin_assume()(Clang)。GCC没有相关特性,所以它也是最早实现标准[[assume]]的,目前就GCC 13支持该特性(等四月发布,该版本对Rangs的支持也很完善)。

现在可以通过宏来玩:

#if defined(__clang__)#define ASSUME(expr) __builtin_assume(expr)
#elif defined(__GNUC__) && !defined(__ICC)#define ASSUME(expr) if (expr) {} else { __builtin_unreachable(); }
#elif defined(_MSC_VER) || defined(__ICC)#define ASSUME(expr) __assume(expr)
#endif

论文当中的一个例子:

void limiter(float* data, size_t size) {ASSUME(size > 0);ASSUME(size % 32 == 0);for (size_t i = 0; i < size; ++i) {ASSUME(std::isfinite(data[i]));data[i] = std::clamp(data[i], -1.0f, 1.0f);}
}

第一个是假设size永不为0,总是正数;第二个告诉编译器size总是32的倍数;第三个表明数据不是NaN或无限小数。

这些假设不会被评估,也不会被检查,编译器假设其为真,依此优化代码。若是假设为假,可能会产生UB。

使用该特性与否编译产生的指令数对比结果如下图。

其次,P2173使得可以在Lambda表达式上使用Attributes,一个例子:

// Any attribute so specified does not appertain to the function 
// call operator or operator template itself, but its type.
auto lam = [][[nodiscard]] ->int { return 42; };int main()
{lam();
}// Output:
// <source>: In function 'int main()':
// <source>:12:8: warning: ignoring return value of '<lambda()>', declared with attribute 'nodiscard' [-Wunused-result]
//    12 |     lam();
//       |     ~~~^~
// <source>:8:12: note: declared here
//     8 | auto lam = [][[nodiscard]] ->int { return 42; };
//       |            ^

注意,Attributes属于closure type,而不属于operator ()

因此,有些Attributes不能使用,比如[[noreturn]],它表明函数的控制流不会返回到调用方,而对于Lambda函数是会返回的。

除此之外,此处我还展示了C++的另一个Lambda特性。

在C++23之前,最简单的Lambda表达式为[](){},而到了C++23,则是[]{},可以省略无参时的括号,这得感谢P1102。

早在GCC 9就支持Attributes Lambda,Clang 13如今也支持。

最后来看P2156,它移除了重复Attributes的限制。

简单来说,两种重复Attributes的语法评判不一致。例子:

// Not allow
[[nodiscard, nodiscard]] auto foo() {return 42;
}// Allowed
[[nodiscard]][[nodiscard]] auto foo() {return 42;
}

为了保证一致性,去除此限制,使得标准更简单。

什么时候会出现重复Attributes,看论文怎么说:

During this discussion, it was brought up that the duplication across attribute-specifiers are to support cases where macros are used to conditionally add attributes to an attribute-specifier-seq, however it is rare for macros to be used to generate attributes within the same attribute-list. Thus, removing the limitation for that reason is unnecessary.

在基于宏生成的时候可能会出现重复Attributes,因此允许第二种方式;宏生成很少使用第一种形式,因此标准限制了这种情况。但这却并没有让标准变得更简单。因此,最终移除了该限制。

目前使用GCC 11,Clang 13以上两种形式的结果将保持一致。

22 Lambdas(P1102R2, P2036R3, P2173R1)

Lambdas表达式在C++23也再次迎来了一些新特性。

像是支持Attributes,可以省略(),这在Attributes这一节已经介绍过,不再赘述。

另一个新特性是P2036提的,接下来主要说说这个。

这个特性改变了trailing return typesName Lookup规则,为什么?让我们来看一个例子。

double j = 42.0;
// ...
auto counter = [j = 0]() mutable -> decltype(j) {return j++;
};

counter最终的类型是什么?是int吗?还是double?其实是double

无论捕获列表当中存在什么值,trailing return type的Name Lookup都不会查找到它。

这意味着单独这样写将会编译出错:

auto counter = [j=0]() mutable -> decltype(j) {return j++;
};// Output:
// <source>:6:44: error: use of undeclared identifier 'j'
// auto counter = [j=0]() mutable -> decltype(j) {
//                                            ^

因为对于trailing return type来说,根本就看不见捕获列表中的j

以下例子能够更清晰地展示这个错误:

template <typename T> int bar(int&, T&&);        // #1
template <typename T> void bar(int const&, T&&); // #2int i;
auto f = [=](auto&& x) -> decltype(bar(i, x)) {return bar(i, x);
}f(42); // error

在C++23,trailing return types的Name Lookup规则变为:在外部查找之前,先查找捕获列表,从而解决这个问题。

目前没有任何编译器支持该特性。

23 Literal suffixes for (signed) size_t(P0330R8)

这个特性为std::size_t增加了后缀uz,为signed std::size_t加了后缀z

有什么用呢?看个例子:

#include <vector>int main() {std::vector<int> v{0, 1, 2, 3};for (auto i = 0u, s = v.size(); i < s; ++i) {/* use both i and v[i] */}
}

这代码在32 bit平台编译能够通过,而放到64 bit平台编译,则会出现错误:

<source>(5): error C3538: in a declarator-list 'auto' must always deduce to the same type
<source>(5): note: could be 'unsigned int'
<source>(5): note: or       'unsigned __int64'

在32 bit平台上,i被推导为unsigned intv.size()返回的类型为size_t。而size_t在32 bit上为unsigned int,而在64 bit上为unsigned long long。(in MSVC)

因此,同样的代码,从32 bit切换到64 bit时就会出现错误。

而通过新增的后缀,则可以保证这个代码在任何平台上都能有相同的结果。

#include <vector>int main() {std::vector<int> v{0, 1, 2, 3};for (auto i = 0uz, s = v.size(); i < s; ++i) {/* use both i and v[i] */}
}

如此一来就解决了这个问题。

目前GCC 11和Clang 13支持该特性。

文章拷贝自:

https://zhuanlan.zhihu.com/p/600302082

http://www.xdnf.cn/news/193303.html

相关文章:

  • 【KWDB 创作者计划】_KWDB产品技术解读
  • 【线性规划】对偶问题的实际意义与重要性质 学习笔记
  • 鼠标获取坐标 vs 相机获取坐标
  • SpringBoot应用原生或docker镜像容器集成Skywalking
  • 数据要素与居民就业的深层联结 数字化转型下的劳动力市场变革
  • 项目上线流程梳理(Linux宝塔面板)
  • 基于Springboot + vue + 爬虫实现的高考志愿智能推荐系统
  • Web基础与HTTP协议
  • 第二章、Isaaclab强化学习包装器(1)
  • 研究:大模型输出一致性:确定性与随机性的场景化平衡
  • 【Android】SettingsPreferenceService
  • (002)Excel 使用图表,统计
  • conda和bash主环境的清理
  • 【优秀三方库研读】【性能优化点滴】odygrd/quill 解决伪共享
  • AcWing 885:求组合数 I ← 杨辉三角
  • vs2022解决 此项目需要MFC库。从visual studio安装程序(单个组件选项卡)为正在使用的任何工具和体系结构安装他们问题
  • JQ6500语音模块详解(STM32)
  • C++ 之 【模拟实现 list(节点、迭代器、常见接口)】(将三个模板放在同一个命名空间就实现 list 啦)
  • 电子电器架构 -- 汽车零部件DV试验与PV试验的定义及关键差异
  • [ 问题解决 ] sqlite3.ProgrammingError: SQLite objects created in a thread can ...
  • mybatis的xml ${item}总是更新失败
  • npm init、换源问题踩坑
  • 【Python数据驱动决策】数据分析与可视化全流程实战指南
  • 论文导读 - 基于边缘计算、集成学习与传感器集群的便携式电子鼻系统
  • Vue基础(7)_计算属性
  • C++核心编程:类与对象全面解析
  • Infrared Finance:Berachain 生态的流动性支柱
  • 车载软件架构 --- AUTOSAR的方法论
  • SwiftUI 8.List介绍和使用
  • 零基础制作Freertos智能小车(教程非常简易)持续更新中....