返回介绍

6.3 Concepts TS

发布于 2024-08-19 12:44:37 字数 42196 浏览 0 评论 0 收藏 0

2009 年,几乎是在概念刚从 C++0x 移除之后,Gabriel Dos Reis、Andrew Sutton 和我开始重新设计概念。这次设计是根据我们最初的想法、从 C++0x 语言设计中得到的经验、使用 C++0x 概念的经验,以及标准委员会的反馈。我们的结论是

  • 概念必须有语义上的意义
  • 概念数量应该相对较少
  • 概念应该基本,而非最小

我们认为 C++ 标准库中包含的大部分单独使用的概念是没有意义的 [Sutton and Stroustrup 2011]。对于任何合理的‘概念’定义,STL 都用不了 103 个‘概念’!我在和 Andrew Sutton 的讨论中大声嚷道,基础代数都没有超过十几个概念!语言设计的讨论可以变得相当热烈。

2011年,在 Andrew Lumsdaine 的敦促下,Alex Stepanov 在 Palo Alto 召集了为期一周的会议。一个相当大的团队,包含了大多数与 C++0x 概念工作密切相关的人,加上 Sean Parent 和 Alex Stepanov,一起讨论从用户的角度来解决这个问题:理想情况下,一个被适度约束的 STL 算法集应当是什么样子?然后,我们回家记录我们以用户为导向的设计,并发明语言机制以接近这个理想设计 [Stroustrup and Sutton 2012]。这一努力重新启动了标准工作,而且使用的是一种全新的、与 C++0x 工作完全不同且更好的方法。2016 年 ISO 出版的概念的 TS(技术规范)[Sutton 2017] 和 C++20 概念(§6.4)就是该会议的直接结果。Andrew Sutton 的实现从 2012 年开始就被用于实验,并作为 GCC 6.0 或更高版本的一部分发布。

在 Concepts TS 中 [Sutton 2017]

  • 概念基于编译期谓词(包括多参数谓词和值参数)。
  • 以使用模式来描述原始要求 [Dos Reis 和 Stroustrup 2006](requires 表达式)。
  • 概念可以用在一般的 requires 子句中,当作模板形参定义中 typename 的替代,也可以当作函数形参定义中类型名的替代。
  • 从类型到概念的匹配是隐式的(没有 concept_map)。
  • 重载中概念间是隐式的关系(通过计算得出,而不需要为概念进行显式细化)。
  • 没有定义检查(至少目前还没有,所以也没有 late_check)。
  • 没有 axiom,但这只是因为我们不想因为一个潜在有争议的特性而让设计更加复杂、产生拖延。C++0x 的 axiom 也可以是一个好起点。

与 C++0x 的概念相比,这里非常强调简化概念的使用,其中的一个主要部分是不要求程序员做显式表达,而让编译器根据明确规定的、简单的算法来解决问题。

支持由用户显式决策的人认为以上的方案重语义而轻语法,并警告会有意外匹配惊吓。最常见的例子是 Forward_iteratorInput_iterator 的区别仅在于语义:Forward_iterator 允许在其序列中做多遍扫描。没有人否认这种例子的存在,但围绕这些例子的重要性以及如何解决它们的争论却没断过(仍然很起劲)。我认为让几个罕见的复杂例子主导设计是大错特错。

Concepts TS 设计是基于这样的看法(有大量经验支持),即上面这样的例子非常罕见(特别是在精心设计的概念中 [Stroustrup 2017]),通常被概念编写者很好地理解,而且常常可以通过在最受约束的概念上添加操作以反映语义上的差异来解决。例如,Forward_iterator/Input_iterator 问题的一个简单解决方案是要求 Forward_iterator 提供一个 can_multipass() 操作。此操作甚至不需要做任何事情;它存在只是为了让概念决策机制能够检查它的存在。因此,不需要专门添加新的语言特性来解决可能出现的意外歧义。

因为这一点经常被忽视,我必须强调,概念是谓词,它们不是类或类层次结构。根本上,我们只是问某个类型一些简单的问题,如你是迭代器吗?并问类型的集合关于它们的互操作的问题,如你们之间能用==来相互比较吗?(§6.3.2)。使用概念时,我们只问那些可以在编译期回答的问题,不涉及运行期求值。潜在的歧义是通过比较类型(或类型集合)所涉及的谓词来检测的,而不是让程序员写决策规则(§6.3.2)。

出于对 C++0x 概念(§6.2.6)中所发生问题的敏感,我们小心翼翼地设计概念,以求使用它们不会隐含显著的编译期开销。即使是 Andrew Sutton 的编译器的早期版本,编译使用了概念的模板的速度也比编译使用变通方案(例如 enable_if(§4.5.1))的程序要

6.3.1 定义检查

在 Palo Alto 会议后几个月之内的某个时间点,Andrew Sutton、Gabriel Dos Reis 和我做出决定,分阶段着手设计和实现概念的语言特性。这样,我们可以从实现的经验中学习,并在设计冻结之前获得早期的反馈。特别是,我们决定推迟实现定义检查(§6.2.4);也就是说,检查并确保模板没有使用并未为其参数指定的功能。考虑 std::advance() 的一个简化版本,它将迭代器在序列中向前移动 n 个位置:

template<Forward_iterator Iter>
void advance(Iter p, int n)
{
    p+=n;  // p 前进 n 个位置
}

Forward_iterator 不提供 +=,只提供 ++,所以定义检查会把它当作错误抓出来。如果不单独(在使用前)检查 advance() 的函数体,我们将只会从 += 的(错误)使用中得到糟糕的实例化时的错误信息,请注意,模板实例化生成的代码总会经过类型检查,所以不做定义检查不会导致运行期错误。

我们认为,概念带来的约 90% 的好处会从使用点检查中收获,而对于那些相对专家级的受约束模板作者来说,没有定义检查也能将就一段时间。这里 90% 显然是基于有限信息的临时估计,但得益于十年间在概念上的工作,我认为这是一个不错的猜测。作为语言特性和库的设计者,对我们来说,更重要的是从使用中获 得经验,这一经验获得的过程始于 Palo Alto 技术备忘录 [Stroustrup and Sutton 2012] 中的 STL 算法示例。我们重视反馈胜于重视理论完整性。这种看法曾是激进的。回顾一下关于概念的文档(在 C++ 和其他语言中),之所以将概念作为语言特性提供,定义检查总是被强调成一个主要原因 [Gregor et al. 2006; Stroustrup and Dos Reis 2003b]。

这种新设计一度被称为轻量概念(Concepts Lite),许多人认为它不完整,甚至没用。但是,我们很快发现,进行定义检查会带来真正的好处 [Sutton and Stroustrup 2011]。

  • 有了定义检查,我们在开发过程中就没办法使用部分概念检查。在构建一个大程序的初始阶段中,不知道全部的需求是非常常见的。部分检查可以让很多错误在早期被发现,并有助于根据早期使用的反馈逐步改进设计。
  • 定义检查使得设计难以拥有稳定的接口。特别是,要往类或者函数中增加调试语句、统计收集、追踪或者遥测之类的支持,就不能不改变类或函数的接口来包含相应功能。这些功能对于类或函数来说很少是根本的,而且往往会随着时间的推移而改变。
  • 当我们不使用定义检查时,现有的模板可以逐渐转换为使用概念。但是,如果我们有定义检查,一个受约束的模板就不能使用一个无约束的模板,因为我们 一般没法知道无约束的模板使用了哪些功能。另外,不管做不做定义检查,一个无约束的模板使用一个有约束的模板都意味着后期(实例化时)检查。

从 2014 年起担任 EWG 主席的 Ville Voutilainen 更为坚定地表示:

我不能支持任何包含定义检查的概念提案。

我们最终可能会得到一种定义检查的形式,但前提是我们能够设计一种机制来避开它,以满足过渡和数据收集的需要。这需要仔细考虑,需要进行实验。C++0x 的 late_check 是不够的。

定义检查的问题是使用的问题,而不是实现的问题。Gabriel Dos Reis 设计并实现了一种名为 Liz 的实验语言,用来测试 Concepts TS 设计中的功能 [Dos Reis 2012],包括定义检查。如果我们找到一种可接受的定义检查形式,我们就可以实现它。

6.3.2 概念使用

简单的示例看起来很像 C++0x 及更早的版本中的样子:

template<Sequence Seq, Number Num>
Num sum(Seq s, Num v)
{
    for (const auto& x : s)
        v += x;
    return v;
}

这里 SequenceNumber 是概念。使用概念而不是 typename 来引入类型的名称,意味着使用的类型必须满足概念的要求。需要注意的是,由于 Concepts TS 不提供定义检查,所以使用 += 不会被概念所检查,而只会在后期、在实例化时检查。以上是最初的开发阶段中可能的做法,稍后我们很可能会更为明确:

template<typename T>
using Value_type = typename T::value_type;  // 简化的别名

template<Sequence Seq, typename Num>
    requires Arithmetic<Value_type<Seq>,Num>
Num sum(Seq s, Num v)
{
    for (const auto& x : s)
        v += x;
    return v;
}

也就是说,我们必须有算数运算符,包括 +=,以供 Sequence 的值类型和我们用作累加器的类型的组合使用。我们不再需要说明 NumNumberArithmetic 会检查 Num 具有所需的一切属性。在这里,Arithmetic 被显式地用作(C++0x 风格的)requires 子句中的谓词。

重载是通过挑选具有最严格要求的函数来处理。考虑标准库中的经典函数 advance 的一个简单版本:

template<Forward_iterator Iter>
void advance(Iter p, int n)  // 将 p 向前移动 n 个元素
{
    while (n--)
        ++p;  // 前向迭代器有 ++,但没有 + 或者 +=
}

template<Random_access_iterator Iter>
void advance(Iter p, int n)  // 将 p 向前移动 n 个元素
{
    p += n;  // 随机迭代器有 +=
}

也就是说,我们应该对提供随机访问的序列使用第二个版本,对只提供前向迭代的序列使用第一个版本。

void user(vector<int>::iterator vip, list<string>::iterator lsp)
{
    advance(vip, 10);  // 使用较快的 advance()
    advance(lsp, 10);  // 使用较慢的 advance()
}

编译器将这两个函数的概念分解为原始(原子)要求,由于前向迭代的要求是随机访问迭代要求的严格子集,所以这个例子可以被解决。

当一个参数类型同时匹配到互相之间不是严格子集的重叠要求时,会产生歧义(编译期错误)。例如:

template<typename T>
    requires Copyable<T> && Integral<T>
T fct(T x);

template<typename T>
    requires Copyable<T> && Swappable<T>
T fct(T x );

int x = fct(2);  // 有歧义:int 满足 Copyable、Integral 和 Swappable
auto y = fct(complex<double>{1,2});  // OK:complex 不满足 integral

程序员唯一能利用的控制机制是在定义概念时为其增加操作。不过对于现实世界的例子来说,这似乎已经足够了。当然,你可以定义一些只在语义上有差异的概念,这样就没有办法根据我们的纯语法概念来区分它们。然而,要避免这样做并不困难。

6.3.3 概念的定义

通过 requires 表达式的使用模式可指定概念的原始要求:

template<typename T, typename U =T>
concept Equality_comparable =
    requires (T a, U b) {
        { a == b } -> bool ; // 使用 == 比较 T 和 U 得到一个 bool 值
        { a != b } -> bool ; // 使用 != 比较 T 和 U 得到一个 bool 值
    };

requires 表达式是 Andrew Sutton 发明的,作为他实现 Concepts TS 的一部分。事实证明它们非常有用,以至于用户坚持认为它们应该成为标准的一部分。

=T 为第二个类型参数提供默认值,因此概念 Equality_comparable 可以用于单个类型。

使用模式的写法是 Bjarne Stroustrup 基于 2003 年的想法 [Stroustrup and Dos Reis 2003b] 在 Palo Alto 的现场会议上发明的。这种写法及其思想并不涉及函数签名或函数表的实现。

不存在特定的机制来表达类型与概念相匹配,但如果有人要这么做,可以使用 C++11 中普通的 static_assert

static_assert(Equality_comparable<int>);       // 成功
static_assert(Equality_comparable<int,long>);  // 成功
struct S { int a; };
static_assert(Equality_comparable<S>);    // 失败了,因为结构体不会
                                          // 自动生成 == 和 != 操作

来自 C++0x(及更早的 [Stroustrup 2003])中的关联类型(associated type)概念也得到了支持:

template<typename S>
concept Sequence = requires(S a) {
  typename Value_type<S>;             // S 必须具有值类型。
  typename Iterator_type<S>;          // S 必须具有迭代器类型。

  { begin(a) } -> Iterator_type<S>;   // begin(a) 必须返回一个迭代器。
  { end(a) } -> Iterator_type<S>;     // end(a) 必须返回一个迭代器。
  { a.begin() } -> Iterator_type<S>;  // a.begin() 必须返回一个迭代器。
  { a.end() } -> Iterator_type<S>;    // a.end() 必须返回一个迭代器。

  requires Same_type<Value_type<S>,Value_type<Iterator_type<S>>>;
  requires Input_iterator<Iterator_type<S>>;
};

注意上面的代码有重复,这是为了可以同时接受 a.begin()begin(a)。缺少统一函数调用让人头疼(§6.1)、(§8.8.3)。

6.3.4 概念名称引导器

从使用中我们学到的一件事情是,基础概念的使用有很多重复。我们在 requires 语句中直接使用了太多的 requires 表达式,并且使用了太多概念。我们的概念要求看起来像新手程序员编写的代码:很少的函数,很少的抽象,很少的符号名。

考虑标准的 merge 家族函数。这些函数都接受三个序列的输入并需要指明这些序列之间的关系。因此就有了对序列类型的三个要求和描述序列元素之间关系的三个要求。第一次尝试:

template<Input_iterator In1, Input_iterator In2, Output_iterator Out>
    requires Comparable<Value_type<In1>,Value_type<In2>>
    && Assignable<Value_type<In1>, Value_type<Out>>
    && Assignable<Value_type<In2>, Value_type<Out>>
Out merge(In1, In1, In2, In2, Out);

这种形式太乏味了;而且,这种引入类型名称的模式非常常见。例如,STL 中至少有四个 merge 函数。乏味且重复的代码非常容易出错,也难以维护。我们很快学会了更多使用多参数概念来定义类型间要求的共同模式:

template<Input_iterator In1, Input_iterator In2, Output_iterator Out>
    requires Mergeable<In1,In2,Out>
Out merge(In1, In1, In2, In2, Out);

对于 Andrew Sutton 来说,这还是太混乱了。他在 2012 年使用概念编写的代码量可能超过任何其他人。他提出了一种机制来表达为满足一个概念的多个类型引入一个类型名集合。这样将 merge 的示例减少到了逻辑上的最少限度:

Mergeable{In1,In2,Out} // 概念名称引导器
Out merge(In1, In1, In2, In2, Out);

仅仅通过尝试,你就能学到很多东西,这真是令人惊叹!同样令人惊叹的是,对于那些尚未经历过这些问题的人,新颖的写法和解决方案在他们那里也会遭遇巨大的阻力。

6.3.5 概念和类型

许多人仍然将概念视为(无论过去和现在)类型的类型这个想法的变体。是的,只有一个类型参数的概念可以看作是一个类型的类型,但只有最简单的用法才适合该模式。

大多数泛型函数(算法)都需要不止一个模板参数,要让这样的函数有意义,这些参数类型必须以某种方式关联起来。因此,我们必须使用多参数概念。例如:

template<Forward_iterator Iter, typename Val>
    requires Equality_comparable<Value_type<Iter>,Val>
Forward_iterator find(Iter first, Iter last, Val v)
{
    while (first!=last && *first!=v)
        ++first;
    return first;
}

至关重要的是,多参数概念直接解决了处理隐式转换和混合类型操作的需求。早在 2003 年,我就和 Gabriel Dos Reis 一起考虑过将每个参数的所有约束条件与其他参数隔离开来说明的可能性 [Stroustrup 2003; Stroustrup and Dos Reis 2003b]。这将涉及

  • 参数化(例如,用值类型来参数化的 Iterator
  • 某种形式的继承(例如,Random_access_iterator 是一个 Forward_iterator
  • 能对一个模板参数应用多个概念的能力(例如,一个 Container 的元素必须满足 Value_typeComparable
  • 这三种技术的组合。

结果是非常复杂的模板参数类型约束。我们认为这种复杂性是不必要的,也无法进行管理。譬如 x+yy+x,其中 xy 具有不同的模板参数类型,XY。在处理各自的模板参数时,我们必须将 XY 以及 YX 进行参数化。在纯面向对象语言中,这看起来很自然。毕竟,有两种方法可以进行 + 运算,一种在 X 的层次结构中,一种在 Y 的层次结构中。然而,我早在 1982 年就拒绝了 C++ 的这个解决方案。要完成这一图景,我们必须添加隐式类型转换(例如,处理 x+22+x)。而多参数概念与 C++ 解决此类场景的方式完全吻合,并避免了大部分的复杂性。

这个决定经过多年的反复审查并得到确认。在设计 C++0x 概念的努力中,人们尝试应用了标准的学术系统,正如在 Haskell 类型类(typeclass)和 Java 约束中可见的。但是,这些做法最终不能提供在大规模使用中所需要的实现和使用上的简单性。

当一个泛型用法符合类型的类型这一模式时,概念能非常优雅地支持它。

  • 类型指定了一组可以(隐式和显式)应用于对象的操作,依赖于函数声明和语言规则,并会指定对象在内存中如何布局。
  • 概念指定了一组可以(隐式和显式)应用于对象的操作,依赖于可以反映函数声明和语言规则的使用模式,并且不涉及对象的布局。因此,概念是一种接口。

我的理想是,能用类型的地方就能用概念,并且使用方式相同。除了定义布局外,它们非常相似。概念甚至可以用来约束那些由其初始化器来确定其类型的变量的类型(受约束的 auto 变量(§4.2.1))。例如:

template<typename T>
concept Integer = Same<T,short> || Same<T,int> || Same<T,long>;

Integer x1 = 7;
int x2 = 9;

Integer y1 = x1+x2;
int y2 = x2+x1;

void f(int&);      // 一个函数
void f(Integer&);  // 一个函数模板

void ff()
{
    f(x1);
    f(x2);
}

C++20 离实现这一理想接近了。为了使该例子能在 C++20 中工作,我们必须在每个 Integer(§6.4)概念后添加一个逻辑上冗余的 auto。另一方面,在 C++20 中,我们可以使用标准库里的 integral 概念来替换明显不完整的 Integer

6.3.6 改进

在 Concepts TS 工作的初期,一个 concept 是一个返回 bool 值的 constexpr 函数(§4.2.7)。这很合理,因为我们把概念看作是编译期的谓词。然后 Gabriel Dos Reis 将变量模板引入到 C++14(§5.2)中。现在,我们有了选择:

// 函数风格:
template<typename T>
concept bool Sequence() { return Has_begin<T>() && Has_end<T>(); }

// 表达式风格:
template<typename T>
concept bool Sequence = Has_begin<T> && Has_end<T>;

我们可以愉快地使用任何一种风格,但是如果两种风格都允许的话,使用概念的用户就必须知道概念定义中使用了哪种风格,否则无法正确使用括号。很快这就成了一个大麻烦。

函数式风格允许概念重载,但是我们只有很少的概念重载例子;于是我们决定没有概念重载也可以。因此,我们进行了简化,只使用变量模板来表达概念。Andrew Sutton 率先全面使用了概念的表达式形式。

我们(Andrew Sutton、Gabriel Dos Reis 和我)始终知道,显式写出 concept 返回 bool 是多余的。毕竟,概念从定义上来看就是一个谓词。然而,我们决定不去搞乱语法而专注于语义上的重要话题。后来,人们总是将冗余的 bool 作为一个反对概念设计的论点,因此我们对其进行了修正,不再提到 bool

删除 bool 是 Richard Smith 提出的一系列改进建议的一部分,其中还包括更精确地描述什么是原子谓词,以及对匹配规则的简化 [Smith and Sutton 2017]。现在,我们使用表达式风格:

// 表达式风格:
template<typename T>
concept Sequence = Has_begin<T> && Has_end<T>;

6.3.7 等效语法

Concepts TS 支持在函数声明中使用概念的三种写法:

  • 为通用起见,显式使用 requires 语句
  • 简略写法,用于表示类型的类型
  • 自然写法(也称为简短写法、常规写法等)

基本思想是,让程序员使用与特定声明的需求紧密匹配的写法,而不会因使用更复杂声明所需的写法而淹没该定义。为了使程序员可以自由选择写法,尤其是允许在项目开发初期或维护阶段随着功能的变化而调整,这些风格的写法被定义为是等效的:

void sort(Sortable &); // 自然写法

等同于

template<Sortable S> void sort(S&); // 简略写法

等同于

template<typename S> requires Sortable<S> void sort(S&);

用户对此感到非常满意,并且倾向于在大多数声明中使用自然和简略写法。但是,有些委员会成员对自然写法感到恐惧(我看不出它是一个模板!),而喜欢使用最显式的 requires 写法,因为它甚至可以表达最复杂的示例(为什么你还要比那更复杂的东西?)。我的解释是,我们对什么是简单有两种看法:

  • 我可以用最简单、最快捷的方式编写代码
  • 我只需要学习一种写法

我赞成前一种观点,认为这是洋葱原则(§4.2)的一个很好的例子。

自然写法成为对概念强烈反对的焦点。我——还有其他人——坚持这种优雅的表达

void sort(Sortable&); // 自然写法

我们看到(过去和现在)这是有用而优雅的一步,可以使泛型编程逐渐变成一种普通的编程方式,而不是一种具有不同语法、不同源代码组织偏好(仅头文件)和不同编码风格(例如模板元编程(§10.5.2))的暗黑艺术。模块解决了源代码组织问题(§9.3.1)。另外,更自然的语法解决了人们总是抱怨的关于模板语法过于冗长和笨拙的问题,我同意这些抱怨。在设计模板时,template<…> 前缀语法不是我的首选。由于人们总是担心能力不强的程序员滥用模板而引起混淆和错误,我被迫接受了这种写法。繁重的异常处理语法(try { … } catch ( … ) { … })也是类似的故事 [Stroustrup 2007]。似乎对于每个新特性,许多人都要求有醒目的语法来防止实际和想象中的潜在问题。然后过一段时间后,他们又抱怨太啰嗦了。

无论如何,有为数不少的委员会成员坚持认为自然语法会导致混乱和误用,因为人们(尤其是经验不足的程序员)不会意识到以这种方式定义的函数是模板, 和其他函数并不相同。我在使用和教授概念的多年里并没有观察到这些问题,因此我并不特别担心这样的假设性问题,但反对意见仍然非常强烈。人们就是知道这样的代码很危险。主要的例子是

void f(C&&); // 危险:C 是一个概念还是类型?

C&& 的含义因 f 是函数模板还是普通的函数而有所不同。在我看来,C&& 语义上的这种差异是 C++11 中最不幸的设计错误,我们应该尝试纠正这一错误,而不是让它影响概念的定义。毫无疑问,误解的可能性是真实存在的,并且一旦该机制被很多人使用时,肯定会 发生。但是,我在现实中没有看到过这种问题,而且我怀疑经验相对丰富的程序员如果遇到这种差异真正会产生影响时,真的会遇到麻烦。换句话说,我认为这是尾巴摇狗的一个示例;也就是说,一个不起眼的例子阻止了一个可以使大量用户受益的特性。

我也很确定,我的目标是使泛型编程尽可能地像普通编程,但这不是普遍共识。仍然有人认为,泛型编程超出了绝大部分程序员的能力。但我没有看到任何证据。

6.3.8 为什么在 C++17 中没有概念?

我曾希望并期望在 C++17 看到概念。在我认为在 2017 年时间窗口可行的扩展(§9.2)中,我把概念看作是对 C++ 程序员的基本词汇的最重大改进。它可以消除很多对丑陋且易出错的模板元编程(§10.5.2)的需求,可以简化库的精确规范定义,并显著改善库的设计。恐怕这就是问题的一部分:概念会直接影响所有投票成员。有些人对旧的方式比较满意,有些人没有概念方面的经验,而有些人则认为它们是未被尝试过的(学院派/理论派)想法。

C++0x 概念(§6.2)的惨败加剧了这种担忧,这导致 我们首先有了技术规范(TS)[Sutton 2017]。我们没有语言特性方面的技术规范经验,但是这似乎值得尝试:Andrew Sutton 在 GCC 中的概念实现仍然比较新,需要谨慎评估。在(2013 年的)Bristol 标准会议上,Herb Sutter 强烈主张采用 TS 路线,而我和 J-Daniel Garcia 警告说可能会有延期。我还指出了将概念与通用 lambda 表达式(§4.3.1)分开考虑的危险性,但是谨慎我们需要更多经验在标准委员会里是很有力的理由。最终,我投票赞成了 Concepts TS。现在我把这看作是一个错误。

2013 年,我们有了一个概念的实现和一个相当不错的规范(主要感谢 Andrew Sutton),但是完成 Concepts TS 还是花了三年的时间。我无法识别出完善 TS 和纳入 ISO 标准在严格程度有什么区别。但是,在 2016 年 Jacksonville 会议上,当对 TS 中描述的概念进行投票以将其纳入标准时,先前的所有反对意见又出现了。反对者似乎只是把概念给忽略了三年。我甚至听到了只对 C++0x 中的概念设计有效、而与 TS 概念设计无关的反对意见。人们再次主张谨慎我们需要更多的经验。据我所知,由于委员会人数增长的部分原因,在 Jacksonville 会议上还没有尝试过概念的人比在 Bristol 时更多。除了我在过去十年中听到的所有反对意见之外,有人提出了全新的反对意见,有人在全体委员会上提出了未经尝试的设计建议,还被认真考虑了。

在 2016 年 2 月的 Jacksonville 会议上,Ville Voutilainen(EWG 主席)提议按照 Concepts TS [Voutilainen 2016c] 把概念放到标准中:

……程序员们非常渴望能使用新的语言特性,现在正是将其交付给他们的时候了。概念化标准库需要花费时间,相信在这个过程中不会发现概念设计有什么大 的问题。我们不应该让程序员一直等待语言特性,只是因为一些假想中的设计问题,这些问题没有证据,甚至有一些反证,很可能根本不存在。为了使世界各地的 C++ 用户受益,让我们在 C++17 里交付概念这一语言特性吧。

他得到了许多人的大力支持,尤其是 Gabriel Dos Reis、Alisdair Meredith(之前是 LWG 主席)和我,但是(尽管 EWG 在本周早些时候投了赞成票)投票结果依然对我们不利:25 票赞成,31 票反对,8 票弃权。我的解释是,用户投了赞成票,语言技术人员投了反对票,但这可能会被认为是酸葡萄吧。

在这次会议上,统一调用语法(§8.8.3)被否决,协程(§9.3.2)被转为 TS,基本上确保了 C++17 只是标准的一个小版本(§8)。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文