概念 (C++)
✍ dations ◷ 2025-10-23 04:48:38 #概念 (C++)
在针对 C++ 进行修订的 C++0x 中,概念 (concept) 和与其相关的一组公设 (axiom) 被提出作为 C++ 模板系统的扩展。它们被设计用来增进编译器发现问题代码所产生的错误消息,并让程序员能在他们所编写的模板中定义模板参数所具备的属性。这些属性让代码能指引编译器做某些优化(除了增进可读性之外),同时也可能透过形式验证工具来检验实现与规格是否相符以增进可靠性。
2009年7月,因为概念被认为还未准备好进入 C++0x,C++0x 委员会决定从标准草案中将其移除。目前有些非正式的计划以某种形式将概念再次纳入标准,但仍未有正式的决定。一个针对概念的初步实现是ConceptGCC(波兰语:ConceptGCC)。
模板类别和函数有必要的对他们使用的类型加上限制。例如STL容器要求它所包含的类型必须是可赋值的。不像动态多态所展现的类别继承阶级,接受Foo&
类型的函数可以接受Foo
的任何子类;只要支持所有模板内使用的操作,任何类别都可以被提供作为模版参数。对函数来说,引数的要求是明确的(必需是Foo
的子类);但是对模版来说,对象必须符合的接口是不明确的。“概念”(concept)提供了一种机制,要求模板参数必须符合特定条件。
引入 concept 的主要目的,是为了改善编译器发现问题代码所产生的错误消息。若程序员尝试在模板中使用不符合其接口需求的类型,编译器应当回报错误。问题在于与模板使用相关的错误消息极难解读,尤其不利于新手。主要有两个原因:首先,错误消息往往将模板参数以原名全数列出,造成消息长度暴增。某些编译器甚至会对简单的错误产生数千字节的错误消息。其次,错误消息通常不会立即指出真正发生问题之处。例如,当程序员试图将不带有拷贝构造函数的类型置入vector
中,第一个错误几乎总是指向vector
内部使用拷贝建构之处。程序员必须有足够的经验和技巧才能够了解真正的错误,是由于使用的类型不满足vector
类的要求(需要拷贝构造函数)。
为了解决上述的问题,C++0x 加入了 这种语言特性。Concept 是一种具名的构造,用来描述类型的需求或是条件限制。在 OOP 中,类似的做法是利用基底类别的定义,当作派生类的最小需求("is-a"的继承方式,派生类都带有基底类别的接口)。而 concept 的定义不限于作为模板参数的限制条件,也可以适用于模板定义(如最后的 concept Stack
)。
模板使用 concept 的一种方法是以 concept 名称取代模板类型指示字class
或typename
。在下面的例子中,若传入模板函数min
的类型不满足 concept LessThanComparable
的要求,编译时将会产生错误,告知用户具现化(instantiate)模板的类型不符合concept LessThanComparable
。
template<LessThanComparable T> const T& min(const T &x, const T &y) { return y < x ? y : x; }
相较于上例的简式用法,更为泛用的concept使用形式如下:
template<typename T> requires LessThanComparable<T> const T& min(const T &x, const T &y) { return y < x ? y : x; }
泛用形中,使用关键字 requires 作为类型需求表列的开始。需求表列由 concept 所构成,可以利用"非"(!) 与 "且"(&&)的符号,将数个 concept 结合,如同逻辑表达式。若用户想避免某个特定的 concept 被模板套用,可以用这样的语法:requires !LessThanComparable<T>
。在模板特化或偏特化中,可以指定类型使用特定的模板实现;而否定的 concept 语法,可以显式地在模板或 concept 中指明被排除的类型条件为何。另外,若需要在需求表列中表达"且"(logical-and)的语义,使用"&&"将多个 concept 链接起来即可。例如若模板中的类型需要设值(assignment)以及拷贝建构(copy-construct),可以使用requires Assignable<T>&&CopyConstructible<T>
。
定义 concept 的方式如下:
auto concept LessThanComparable<typename T>{ bool operator<(T, T);}
此处为 concept LessThanComparable
宣告,说明若类型 T
有一个双参数的函数:operator <
,且函数传回值为bool
,则类型 T
满足 concept LessThanComparable
。函数 operator <
可以是全局或是成员函数。
C++0x 为了避免 concept 的误用,除非用户显式指明,编译器不会主动认定类型符合 concept (隐式套用 concept)。为了避免繁琐的指明,此处关键字auto
代表只要类型带有 concept 中指定的操作,它即是符合该 concept 的一个类型。若没有加上auto
,则必须使用concept_map
来指明类型符合特定的 concept。
concept 也可以包含多种类型。例如以下的 concept Convertible
,表示类型 T
可转换为 U
。
auto concept Convertible<typename T, typename U>{ operator U(const T&);}
在模板中使用涉及多态别的 concept,必须使用泛用形式:
template<typename U, typename T> requires Convertible<T, U> U convert(const T& t) { return t; }
Concept 可以是其他 concept 的构件。在下例中,InputIterator
的第一个参数 Iter
必须符合 concept Regular
:
concept InputIterator<typename Iter, typename Value>{ requires Regular<Iter>; Value operator*(const Iter&); Iter& operator++(Iter&); Iter operator++(Iter&, int);}
另一方面,concept 之间也能带有派生关系。如同类的继承,满足派生 concept 的类型也必须满足基底 concept,语法上也和类继承相同:
concept ForwardIterator<typename Iter, typename Value> : InputIterator<Iter, Value>{ // 在此加上 ForwardIterator 的其它要求}
Concept 中也可宣告关系类型(associated type),以 typename 宣告。模板使用 concept 时,模板引数必须要提供相关类型的定义。
concept InputIterator<typename Iter>{ typename value_type; typename reference; typename pointer; typename difference_type; requires Regular<Iter>; requires Convertible<reference, value_type>; reference operator*(const Iter&); // 解參考 Iter& operator++(Iter&); // 前置遞增 Iter operator++(Iter&, int); // 後置遞增 // ...}
映射概念
Concept map
可以将类型"映射"到特定的 concept,告知编译器使用的类型是"如何"符合 concept。
concept_map InputIterator<char*>{ typedef char value_type ; typedef char& reference ; typedef char* pointer ; typedef std::ptrdiff_t difference_type ;};
这个 concept_map
定义 char*
符合 concept InputIterator
,并且一一声明所需的关系类型。
concept_map
可以宣告成模板,下面的例子声明所有的指针类型都符合 concept InputIterator
。
template<typename T> concept_map InputIterator<T*>{ typedef T value_type ; typedef T& reference ; typedef T* pointer ; typedef std::ptrdiff_t difference_type ;};
concept_map
可以作为一个迷你类型,在其中置入函数的定义与其它用来定义类的相关构件。
concept Stack<typename X>{ typename value_type; void push(X&, const value_type&); void pop(X&); value_type top(const X&); bool empty(const X&);};template<typename T> concept_map Stack<std::vector<T> >{ typedef T value_type; void push(std::vector<T>& v, const T& x) { v.push_back(x); } void pop(std::vector<T>& v) { v.pop_back(); } T top(const std::vector<T>& v) { return v.back(); } bool empty(const std::vector<T>& v) { return v.empty(); }};
在这里,concept Stack
定义了需要的函数以及关系类型,而 concept_map
定义如何以 std::vector
实现底层的操作,每个 concept Stack 里的函数都可以转接到 std::vector
的函数调用。因此,concept_map
能在不改变原类型(类别)的定义下,完成接口转换(interface adaptation)。
最后值得一提的是,一些模板的要求可以使用编译期断言(static assertion)。它们可以验证一些模板的要求,不过实际上是针对不同的问题。
C++0x 提供了公设 (axiom) 用来表达概念的语义属性。举例来说,我们可以用公设 Associativity
来定义概念 Semigroup
:
concept Semigroup< typename Op, typename T> : CopyConstructible<T>{ T operator()(Op, T, T); axiom Associativity(Op op, T x, T y, T z) { op(x, op(y, z)) == op(op(x, y), z); }}
编译器可以利用公设所表达的语义做些原本不被允许的优化,因为这些优化可能会在程序可见的行为上有副作用 (其除了少数的例外,其中之一是回返值优化 (RVO))。在上述的例子中,编译器可能会重新安排 operator()
调用的次序。前提是 Op
和 T
与概念 Semigroup
有映射关系。
公设也能在软件验证,软件测试以及其它程序分析和转换上有所帮助。
相关
- 二磷酸脱氧鸟苷去氧鸟苷二磷酸(Deoxyguanosine diphosphate;dGDP)是较为常见的核酸GTP之衍生物,比GTP少了一个位在五碳糖2号碳上的-OH基,含有两个磷酸基团。
- 胡安·何塞·罗松胡安·何塞·罗松·佩雷斯(西班牙语:Juan José Rosón Pérez,1932年9月25日-1986年8月19日)是西班牙政治家,前内政大臣。1932年9月25日出生于卢戈省贝塞雷亚。父母是加利西亚人
- 一般保护错误一般保护错误(英语:General protection fault,缩写:GPF)是在英特尔x86和AMDx86-64架构和其它架构中的一种错误(或者一种中断),指正在运行的程序(内核或用户态程序)违反处理器架构中的
- 振荡线圈变换器振荡线圈变换器(Ringing Choke Converter,缩写为:RCC), 是一种适合小功率离线直流输出的开关电源。据传,振荡线圈变换器是在开关电源的概念出现之后,由日本的电子工程师发明,并在日
- 夏水夏水,古水名。据《水经注》,故道从湖北沙市市东南分江水东出,流经今监利县北,折东北至沔阳县治附近入汉水。自此以下的汉水,也兼称楚夏水。也可能另有一条在上古时期亦称之为夏水
- 阿尔瓦罗·西塞·维埃拉阿尔瓦罗·若阿金·德·梅洛·西塞·维埃拉(葡萄牙语:Álvaro Joaquim de Melo Siza Vieira,1933年6月25日-),通称阿尔瓦罗·西塞(Álvaro Siza,葡萄牙语发音:.mw-parser-output .IPA
- 越南通讯社越南通讯社(越南语:Thông tấn xã Việt Nam/通訊社越南?;英语:Vietnam News Agency,简称VNA),简称“越通社”,是越南国家通讯社,始建于1945年9月2日。越通社是越南官方消息来源,负责
- 吕世浩吕世浩(1971年-),金门人,历史学家。国立台湾大学历史博士、北京大学考古学及博物馆学博士,曾于台湾大学历史学系任教。曾随毓鋆于奉元书院
- 郭岱琦郭岱琦(阿美语:Kanas Kociang,1981年10月16日-),前台湾棒球选手,曾效力于中华职棒统一7-ELEVEn狮队,守备位置为外野手,2017.12-2019.04为隽品机电工程有限公司的老板,由于不擅经营、账务不明导致公司亏损,于2019被股东撤除职务。。其胞兄为前中信鲸队外野手郭岱咏。业余时代是辅仁大学的中心打者,2004年中职选秀会首轮加盟统一狮,12年的职棒生涯共出赛819场,击出587支安打、包括66发本垒打,平均打击率为2成63、上垒率3成35与长打率4成07。2006年底赴美进
- 米哈伊尔·格林卡米哈伊尔·伊万诺维奇·格林卡(俄语:Михаи́л Ива́нович Гли́нка,1804年6月1日-1857年2月15日),又译葛令卡,第一个获得广泛声誉的俄国作曲家,对后来的俄罗斯音乐创作特别是对俄国浪漫乐派强力集团有重要影响,被誉为俄国交响乐的奠基人。曾经的俄罗斯国歌《爱国歌》即是格林卡的作品。格林卡出生于一个地主家庭,早年曾参加过家中的农奴乐队,掌握了小提琴演奏和许多民歌。1818~1822 年,他到圣彼得堡贵族寄宿学校学习。1824年,他到交通部任职,但后来决定从事音乐事业。1830年到意