美文网首页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:两个极端与概念的救赎

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