美文网首页
C++20学习:concept用法介绍

C++20学习:concept用法介绍

作者: khaos | 来源:发表于2021-01-17 16:48 被阅读0次

    概述

    众所周知,C++的泛型强大,很多语言都学了,比如C#、Java,虽然Java的泛型只是语法糖,不值得一提。

    C++的template实际上是创造了一种新的语言,这个语言是编译期的,相当于编译器把代码解释成C++语言,然后再编译成机器指令。STL的诞生,是颠覆性的创举,然而STL所用到的泛型还只是C++template中其中一部分特性。

    C++使用template实现泛型算法,我们来看下一个简单的例子。

    template<typename T>
    T inc(T a)
    {
        return ++a;
    }
    

    这个简单几行代码,就实现了适合所有C++内建类型的自增函数,甚至只要class支持加法++运算的重载,也都是支持的。

    设计一个原则,先宽后严,其实非常困难的。对于C++也不例外。上面的代码,对于T类型的要求,只是概念层面,无法通过函数的声明能够直接看出T的限制。只有用户写完代码,一看才知道有编译错误,而且糟糕的是甚至没有编译错误,真的会怼天怼地。比如:

    using namespace std;
    
    template<typename T>
    T inc(T a)
    {
        return ++a;
    }
    
    int main(int argc, const char * argv[])
    {
        int a = 0;
        cout << inc(&a) << endl;
        return 0;
    }
    

    上面代码inc函数参数a不能传递指针,编译的时候没有错误提示,只有到运行的时候可能会出问题。

    另外,template的编译错误提示又做得不好,这也是C++最为诟病的地方。于是concept特性一直呼声比较高。

    concept

    concept一个用处,就是对T进行限制,让编译器自动检查T是否符合函数的要求。

    我们先看下,不用concept,如何判断T是否符合要求:

    template<typename T>
    T inc(T a)
    {
        static_assert(std::is_integral<T>::value);
        return ++a ;
    }
    
    int main(int argc, const char * argv[])
    {
        int a = 0;
        cout << inc(&a) << endl;
        return 0;
    }
    

    这个时候,编译器就会编译错误了,inc函数不能传递指针,T只有满足是整型才可以。虽然可以解决问题,但是T的检查是在函数内实现的,只有出错了,用户才知道。不然从函数inc的声明直观看出T的要求。

    我们看看使用concept如何实现的。代码如下:

    template<typename T>
    concept Integral = std::is_integral<T>::value;
    
    template<Integral T>
    T inc(T a)
    {
        return ++a ;
    }
    
    int main(int argc, const char * argv[])
    {
        int a = 0;
        cout << inc(&a) << endl;
        return 0;
    }
    

    这个实现,使用concept关键字。如果我们预期,编译不通过,我们在看看编译错误:

    main.cpp:40:3: note: constraints not satisfied
    main.cpp: In instantiation of ‘T inc(T) [with T = int*]’:
    main.cpp:48:19:   required from here
    main.cpp:37:9:   required for the satisfaction of ‘Integral<T>’ [with T = int*]
    main.cpp:37:41: note: the expression ‘std::is_integral<_Tp>::value [with _Tp = int*]’ evaluated to ‘false’
       37 | concept Integral = std::is_integral<T>::value;
    

    g++的错误提示,已经很容易看出来,inc函数传递指针是不满足concept的要求的。现状从inc函数的声明,也能直接看出对T的要求,T要是Integer类型。对于函数的设计者来说,你起一个比较直观的名字,就可以传达你的约束了。

    上面concept只是一种写法,还有三种写法如下:

    • 写法2
    template <typename T>requires Integral<T>
    T inc(T a)
    {
        return ++a ;
    }
    
    • 写法3
    template <typename T>
    T inc(T a) requires Integral<T>
    {
        return ++a ;
    }
    

    这种写法其实和写法2,差别不大,只是requires语句放的位置不同而已。

    • 写法4
    Integral auto inc(Integral auto a)
    {
        return ++a ;
    }
    

    这种也是我们的Bjarne Stroustrup老爷子最喜欢的语法。虽然看上去比较怪异,template关键字没了,多了auto关键字,也许是从c++的实现角度,使用auto关键字比较统一吧。

    我觉得第一种写法,比较符合承上启下,与template语法差异不大,没有那么大的语法跳变,比较容易理解。

    讨论

    一种concept特性,四种写法,买一赠三,不带含糊的,就看你脑袋瓜子够不够用。之所以,为啥要有四种写法。接下来再好好学习,知其然知所以然吧。

    相关文章

      网友评论

          本文标题:C++20学习:concept用法介绍

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