• C++ concept的概念和使用


    C++ concept的概念和使用

    concept 这套语法优化了模板编程,替代了原来的SFINAE编程模式,通过给模板类参数加入限制条件,使得代码可读性更强、编译更快、错误提示更容易理解。

    SFINAE编程模式

    SFINAE 是"Substitution Failure Is Not An Error"的简称。

    模板实例化时类型推演失败不会报错,而是当成一个语言特性。利用模板通用定义和模板特化,对于不同的模板参数给予不同的实现。

    int T::* 表示指向T的成员的指针。

    Keywords

    concept

    定义类似模板的东西,在requires 中使用。

    template <class T>
    concept integral = std::is_integral_v<T>;
    
    • 1
    • 2

    requires

    有以下作用:

    用于定义concept。

    template <typename T>
    concept ILabel = requires(T v)
    {
        {v.buildHtml()} -> std::convertible_to<std::string>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    用于为模板施加限制。

    template 
    requires CONDITION
    void DoSomething(T param) { }
    
    • 1
    • 2
    • 3

    另一种写法

    template 
    void DoSomething(T param) requires CONDITION
    { 
    }
    
    • 1
    • 2
    • 3
    • 4

    代码示例

    我自己的例子
    template 
    concept should_be_float = std::is_floating_point_v;
    
    template  requires should_be_float
    T add(T a, T b) { return a + b; }
    
    
    void concept_test()
    {
        auto r = add(1.2, 3.3);
        auto r2 = add(1, 2);  // 编译不过
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    STL的例子
    template 
    concept assignable_from = is_lvalue_reference_v<_LTy>
        && common_reference_with&, const remove_reference_t<_RTy>&>
        && requires(_LTy _Left, _RTy&& _Right) {
            { _Left = static_cast<_RTy&&>(_Right) } -> same_as<_LTy>;
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    concepts library

    头文件中定义了一些预定义的concept。

    • same_as

    • derived_from

    • convertible_to

    • common_reference_with

    • common_with

    • integral

    • signed_integral

    • unsigned_integral

    • floating_point

    • assignable_from

    • swappable / swappable_with

    • destructible

    • constructible_from

    • default_initializable

    • move_constructible

    • copy_constructible

    • boolean-testable

    • equality_comparable

    • movable

    • copyable

    • semiregulasr

    • regular

    • invocable

    • predicate

    • relation

    • equivalence_relation

    • strict_weak_order

    语法糖

    auto 关键字

    省去template,将

    template 
    auto foo(T p) {}
    
    • 1
    • 2

    改写为:

    auto foo(auto p) {} 
    
    • 1

    可以极为化简模板函数代码,比如:

    auto foo2(auto a, auto b)
    {
        return a + b;
    }
    
    int main()
    {
        std::cout << foo2(1.2, 2) << std::endl;
        std::cout << foo2(1, 2) << std::endl;
        std::cout << foo2(1.2, 1.6) << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    与concepts合用

    void print3(const std::ranges::range auto& c)
    {
        for (auto && elem : c)
        {
            std::cout << elem << ", ";
        }
        std::cout << std::endl;
    }
    int main()
    {
        std::vector c{ 1,2,4,5,12,23,88,23, 17 };
        print3(c);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    template + concept 更加可读的写法

    用concept名字替代关键字 template 或者 class

    template 
    auto sum(const std::vedctor& vec)
    {
        // return ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    还可以跟模板可变参数合用。

    template 
    concept number = std::integral || std::floating_point;
    
    template 
    auto sum_all(TArgs ...args)
    {
        return (... + args);
    }
    
    int main()
    {
        std::cout << sum_all(1, 2, 3) << std::endl;
        std::cout << sum_all(1, 2.2, 3) << std::endl;
        std::cout << sum_all(1.1, 2.2, 3.3) << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结合auto关键字,能有更简洁的写法。

    template 
    concept number = std::integral || std::floating_point;
    
    auto sum_all(const number auto & ...args)
    {
        return (... + args);
    }
    
    int main()
    {
        std::cout << sum_all(1, 2, 3) << std::endl;
        std::cout << sum_all(1, 2.2, 3) << std::endl;
        std::cout << sum_all(1.1, 2.2, 3.3) << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    鸭子类型

    利用concepts语言特性,得到类似Ruby语言那样的鸭子类型(duck typing)和mixin特性(例如Ruby的Enumerable)。

    鸭子类型是指“如果一个动物走起来像鸭子,叫起来也像鸭子,那么它就是鸭子”。也就是说不关心一个类型是不是继承于某个基类,而是看类型是否实现了某些方法(在concepts中可以扩展成某些成员变量、成员类型、返回特定类型的方法等等)。基于鸭子类型实现了mixin,mixin是指非侵入式地为某个鸭子类型的所有类型赋予某方面的能力。例如Ruby的Enumerable对所有实现了“取值”和“下一个”的方法的类型,提供了一系列迭代器相关的方法(first, last, find 等等)。鸭子类型的好处是在于其“非侵入式”。

    用concept定义一个鸭子类型。

    template 
    concept IClock = requires(T c) {
        c.start();
        c.stop();
        c.getTime();
    };
    
    void foo3(IClock auto& clock)
    {
        clock.start();
        clock.stop();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其他

    尾部返回类型

    引入尾部返回类型的背后原因是模板函数根据模板参数确定返回类型。

    template
    ??? multiply(A a, B b) { return a*b}
    
    
    • 1
    • 2
    • 3

    C++编译器是从左往右处理代码(就是lexical order),当扫描到返回类型的时候尚未扫描到模板参数a和b,因此无法使用decltype(a*b)这个东西。Trailing Return Types解决这个问题的方法如下代码所示:

    template
    auto multiply(A a, B b) -> decltype(a*b) { return a * b; }
    
    • 1
    • 2

    其他原因是:

    1. 跟lambda定义的风格一样;
    2. 名字比返回类型重要,应该在前面;
    3. 与数学上函数定义(R -> R)一致。

    References

    1. [C++ Trailing Return Types ‐ Daniel Sieger](https://www.danielsieger.com/blog/2022/01/28/cpp-trailing-return-types.html#:~:text=Trailing return types are an alternative syntax introduced,return type after the function name and parameters%3A)
    2. C++20 Concepts - a Quick Introduction - C++ Stories (cppstories.com)
    3. Concepts library (since C++20) - cppreference.com
    4. Predefined C++20 Concepts: Callables - C++ Stories (cppstories.com)
    5. Requires-expression | Andrzej’s C++ blog (wordpress.com)
  • 相关阅读:
    flink 入门(一)
    [随手记]selector是如何生效的
    介绍两款代码自动生成器,帮助提升工作效率
    创建型模式-设计模式
    瑞芯微 | I2S-音频基础分享
    springboot springcloud gateway 中的 undertow 禁止接收trace请求(修复漏洞)
    开机自启动Linux and windows
    在公共Linux服务器上创建自己的python虚拟环境
    Java中方法的注意事项
    JAVA毕业设计服装连锁店后台管理系统计算机源码+lw文档+系统+调试部署+数据库
  • 原文地址:https://blog.csdn.net/Ahxing1985/article/details/132795560