美文网首页C++
C++20:两个极端与概念的救赎

C++20:两个极端与概念的救赎

作者: 奇点创客 | 来源:发表于2020-02-25 23:39 被阅读0次

原文详见:C++20: Two Extremes and the Rescue with Concepts

我们在上一篇文章中完成了 C++20 的概览。现在,是时候来探究一下细节了。有什么比概念更适合作为起点呢?

必须承认:我本人就是一个铁杆的概念粉。那么,就让我们从一个刺激的例子开始吧!

两个极端


在 C++20 之前,我们有两种完全不同的方式来考虑函数或类:可以在具体类型或泛型类型上定义函数或类。在第二种情况下,我们称它们为函数模板或类模板。那么,每种方法都有什么问题呢?

太过具体

为每个特定类型定义一个函数或类是一项相当艰巨的工作。为了避免这种负担,类型转换常常会来拯救我们,然而,这种拯救往往也是一种诅咒。

// tooSpecific.cpp
#include <iostream>

void needInt(int i)
{
    std::cout << "int: " << i << std::endl;
}

int main()
{   
    std::cout << std::boolalpha << std::endl;

    double d{1.234};                             // (1)N
    std::cout << "double: " << d << std::endl;
    needInt(d);                                  // (2)            
    
    std::cout << std::endl;
    
    bool b{true};                                // (3)
    std::cout << "bool: " << b << std::endl;
    needInt(b);                                  // (4)
    
    std::cout << std::endl; 
}

在第一种情况下(第 1 行),我以 double 开头,以 int 结尾(第 2 行)。在第二种情况下,我以 bool 开始(第 3 行),也以 int 结束(第 4 行)。

缩窄转换

使用 double 去调用 getInt(int a) 可以会触发缩窄转换。缩窄转换是指准确性下降的转换。我认为这不是你想要的。

整体提升

但是相反的情况也不好。使用 bool 去调用getInt(int a) 会将 bool 提升为 int。震惊!许多 C++ 开发者竟然不知道 bool 类型相加中时会得到哪种类型。

template <typename T>
auto add(T first, T second){
    return first + second;
}

int main(){
    add(true, false);
}

C++ Insights 里告诉了你真相。

函数模板 add 的特化版本使用 int 创建了完整的返回类型(第 6 - 12 行)。

我坚信,为了方便起见,C/C++ 中需要有种神奇的转换来处理函数只接受特定类型这一事实。

那么,让我们反过来做。不写针对具体类型的代码,而写是通用的。也许,用模板编写通用代码就是我们的救命稻草。

太过通用

这是我的第一次尝试。排序是一个通用的算法。如果容器中的元素是可排序的,则排序就应该应适用于每个容器。那就让我们将 std::sort 应用于 std::list 。

// sortList.cpp
#include <algorithm>
#include <list>

int main()
{
    std::list<int> myList{1, 10, 3, 2, 5};
    
    std::sort(myList.begin(), myList.end());  
}

哇偶!当我试着去编译这段小程序得到了什么?!



我不想去解读这些错误消息。到底哪里出问题了?让我们仔细看一下所使用的 std::sort 的重载签名。

template< class RandomIt >
void sort( RandomIt first, RandomIt last );

std::sort 使用了一些奇怪的参数,比如 RandomIt。RandomIt 代表一个随机访问迭代器。这就是出现大量错误消息的原因,模板因此而声名狼藉。std::list 只提供了一个双向迭代器,但是 std:sort 需要一个随机访问迭代器。std::list 的结构使这一点更加显而易见。

当你仔细研究 cppreference.com 页面上有关 std::sort 的文档时,你会发现一些非常有趣的东西:std::sort 对类型的要求。

概念的救赎

概念是救星,因为他对模板参数施加了语义约束。
以下是 std::sort 提到的类型要求:

std::sort 上的类型要求就是概念。关于概念的简短介绍,请阅读我之前的文章 C++20:四大件。特别地,std::sort 需要一个遗留随机访问迭代器。我稍微修改了一下来自 cppreference.com 上的示例,以让我们来仔细看看这个概念。

template<typename It>
concept LegacyRandomAccessIterator =
  LegacyBidirectionalIterator<It> &&        // (1)
  std::totally_ordered<It> &&
  requires(It i, typename std::incrementable_traits<It>::difference_type n) {
    { i += n } -> std::same_as<It&>;        // (2)
    { i -= n } -> std::same_as<It&>;
    { i +  n } -> std::same_as<It>;
    { n +  i } -> std::same_as<It>;
    { i -  n } -> std::same_as<It>;
    { i -  i } -> std::same_as<decltype(n)>;
    {  i[n]  } -> std::convertible_to<std::iter_reference_t<It>>;
  };

这就是关键所在。如果它支持概念LegacyRandomAccessIterator(第 2 行)和所有其他要求,那么它就支持这个概念。例如,第 2 行中的要求表示类型为 It: {i += n} 的值是一个有效的表达式,它返回一个 i&。总的来说就是,std::list 支持一个 LegacyBidirectionalIterator。

不容置疑,这一节的技术性很强。我们来试试。有了概念,你可以期待一个简洁的错误信息,如以下:

当然,这个错误消息是假的,因为还没有编译器实现了 C++20 语法的概念。MSVC 19.23 部分地支持它们,GCC 上是概念的一个以前的版本。cppreference.com 提供了概念当前状态的更多细节。

我提到过 GCC 支持旧版本的概念吗?

概念:从历史中走来

我第一次听说概念是在 2005 年到 2006 年之间。这让我想起了 Haskell 类型的类。Haskell 中的类型类是类似类型的接口。下面是 Haskell 类型类层次结构的一部分。



但是,C++ 的概念与之不同:

  • 在 Haskell 中,类型必须是类型类的实例。在C++ 20 中,类型必须满足概念的要求。
  • 概念可以用在模板的非类型参数上。例如,像 5 这样的数字是非类型参数。当你想要一个包含 5 个元素的 std::array 时,你可以使用非类型的参数 5:std::array<int, 5> myArray。
  • 概念不会增加运行时开销

最初,概念应该是 C++11 的关键特性,但是在 2009 年 7 月于法兰克福举行的标准化会议上,它被删除了。引用 Bjarne Stroustrup 的话是:“C++ 0x 的概念设计演变成了一个复杂的怪物。”几年后,第二次尝试也不成功:C++17 标准中删除了概念。最中,它们终于成了 C++ 20 的一部分。

接下来?

不出所料,我的下一篇文章还是关于概念的。我会举很多例子,来说明模板参数的语义约束是什么意思。

相关文章

  • C++20:两个极端与概念的救赎

    原文详见:C++20: Two Extremes and the Rescue with Concepts 我们在...

  • C++20:概念之细节

    原文详见:C++20: Concepts, the Details 在我的上一篇文章 C++20:两个极端和概念的...

  • 对 错

    “对就是对,错就是错!”。人们赋予任何事物“对错”的概念。 对与错,形成两个极端。极端,如同黑...

  • C++20:标准库

    原文详见:C++20: The Library 在上篇文章 C++20:核心语言 中我们介绍了 C++20 的核心...

  • 《美丽心灵》电影推荐-天才的救赎之路

    推荐指数:4.5颗 天才的极端崩溃; 爱情的温暖救赎。 现在很多电影都或多或少包含这两个因素,但每次看见类似的桥段...

  • C++20:核心语言

    原文详见:C++ 20: The Core Language 在上篇文章 C++20:四大件 中,我们对概念(co...

  • 理解与包容——《人的行动》读书笔记22

    原文 正常与变态这两个极端的概念,只有在人类学意义上,把那些随大流的人与那些特立独行的人区别开来时,才用得着;在生...

  • 生存在一个二元的世界

    其实我们生存在一个二元的世界,光明与黑暗,并且寒冷与炎热等等。 对于这些例子,是两个极端,当然了,人也是两个极端,...

  • C++20:并发

    原文详见:C++20: Concurrency 本篇是 C++20 概览系列的最后一篇。今天我将介绍 C++ 新标...

  • 不足与不足 的两个极端

    ———欲使其灭亡,必让其疯狂 人类的进步源自于对欲望无止境的追求和满足,当然人类的灭亡也可能源...

网友评论

    本文标题:C++20:两个极端与概念的救赎

    本文链接:https://www.haomeiwen.com/subject/grgqchtx.html