C++ concept的概念和使用

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

SFINAE编程模式

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

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

Keywords

concept

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

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

requires

有以下作用:

用于定义concept。

template <typename T>
concept ILabel = requires(T v)
{
    {v.buildHtml()} -> std::convertible_to<std::string>;
};

用于为模板施加限制。

template <typename T>
requires CONDITION
void DoSomething(T param) { }

另一种写法

template <typename T>
void DoSomething(T param) requires CONDITION
{ 
}

代码示例

我自己的例子

template <typename T>
concept should_be_float = std::is_floating_point_v<T>;

template <typename T> requires should_be_float<T>
T add(T a, T b) { return a + b; }


void concept_test()
{
    auto r = add(1.2, 3.3);
    auto r2 = add(1, 2);  // 编译不过
}

STL的例子

template <class _LTy, class _RTy>
concept assignable_from = is_lvalue_reference_v<_LTy>
    && common_reference_with<const remove_reference_t<_LTy>&, const remove_reference_t<_RTy>&>
    && requires(_LTy _Left, _RTy&& _Right) {
        { _Left = static_cast<_RTy&&>(_Right) } -> same_as<_LTy>;
    };

concepts library

<concepts>头文件中定义了一些预定义的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 <typename T>
auto foo(T p) {}

改写为:

auto foo(auto p) {} 

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

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;
}

与concepts合用

void print3(const std::ranges::range auto& c)
{
    for (auto && elem : c)
    {
        std::cout << elem << ", ";
    }
    std::cout << std::endl;
}
int main()
{
    std::vector<int> c{ 1,2,4,5,12,23,88,23, 17 };
    print3(c);
}

template + concept 更加可读的写法

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

template <std::integral T>
auto sum(const std::vedctor<T>& vec)
{
    // return ...
}

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

template <typename T>
concept number = std::integral<T> || std::floating_point<T>;

template <number ...TArgs>
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;
}

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

template <typename T>
concept number = std::integral<T> || std::floating_point<T>;

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;
}

鸭子类型

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

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

用concept定义一个鸭子类型。

template <typename T>
concept IClock = requires(T c) {
    c.start();
    c.stop();
    c.getTime();
};

void foo3(IClock auto& clock)
{
    clock.start();
    clock.stop();
}

其他

尾部返回类型

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

template<typename A, typename B>
??? multiply(A a, B b) { return a*b}

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

template<typename A, typename B>
auto multiply(A a, B b) -> decltype(a*b) { return a * b; }

其他原因是:

  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)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,151评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,465评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,429评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,147评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,149评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,614评论 1 315
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,965评论 3 429
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,950评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,486评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,524评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,640评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,228评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,976评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,552评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,215评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,714评论 2 366

推荐阅读更多精彩内容