函数模板和缩写函数模板之间的等价性 [英] Equivalence between function templates and abbreviated function templates

查看:41
本文介绍了函数模板和缩写函数模板之间的等价性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下所有标准参考均指当前的 ISO 标准工作草案,生成于 2020-06-22.


[dcl.fct]/18 指出 [提取,强调我的]:

<块引用>

缩写函数模板是一种函数声明,它具有一个或多个通用参数类型占位符 ([dcl.spec.auto]).缩写的函数模板等价于一个函数模板([temp.fct]),它的template-parameter-list包含一个发明类型template-parameter 对于函数声明的每个泛型参数类型占位符,按出现顺序. [...]

这样,函数声明的以下内容可以等价:

template 无效 f(T);无效 f(自动);//重新声明

然而,我们可能会注意到,[dcl.fct]/18 指出

<块引用>

[...]

这些声明与以下声明在功能上等效(但不等效).

[...]

这可能(我不确定如何解释)可能与前一段中的等价陈述相冲突.

现在,GCC 10.1.0 和 Clang 10.0.0(以及 GCC:HEAD 和 Clang:HEAD)在这里都有一些混合行为.如果我们声明一个函数模板,然后使用混合经典函数模板语法和缩写函数模板语法定义它(/重新声明它),Clang 接受大多数情况(定义一个先前声明的函数),而 GCC 拒绝所有(见(尝试)) 重新声明为单独声明的函数,随后在重载解析中出现歧义失败):

//A1: Clang OK,GCC 错误模板 无效a(T);void a(auto) {}//B1: Clang OK,GCC 错误无效b(自动);模板 无效 b(T) {}//C1: Clang OK,GCC 错误模板 <typename T, typename U>void c(T, U);void c(auto, auto) {}//D1: Clang OK,GCC 错误模板 <typename T, typename U>void d(T, U);模板 void d(T, 自动) {}//E1: Clang 错误,GCC 错误模板 void e(T, auto);模板 void e(auto, T) {}int main() {一(0);//Clang OK,GCC 错误.乙(0);//Clang OK,GCC 错误.c(0, '0');//Clang OK,GCC 错误.d(0, '0');//Clang OK,GCC 错误.e(0, '0');//Clang 错误,GCC 错误.}

奇怪的是,如果我们使函数模板成为类成员函数模板,GCC 和 Clang 都接受 A1D1 的情况,但都拒绝最后的情况 E1 以上:

//A2:好的结构体{模板 无效a(T);};void Sa::a(auto) {}//B2:好的结构体{无效b(自动);};模板 void Sb::b(T) {}//C2:好的结构体{模板 <typename T, typename U>void c(T, U);};void Sc::c(auto, auto) {}//D2:好的结构体{模板 <typename T, typename U>void d(T, U);};模板 void Sd::d(T, auto) {}//E2:错误结构体{模板 void e(T, auto);};模板 void Se::e(auto, T) {}

带有以下错误消息:

<块引用>

海湾合作委员会

error: 没有声明匹配 'void Se::e(auto:7, T)'注意:候选人是:'模板void Se::e(T, auto:6)'

叮当声

 错误:'e' 的外部定义不匹配'Se' 中的任何声明

现在,类型模板参数的名称不需要与函数模板的重新声明(或定义)保持一致,就像只是命名泛型类型占位符一样.

GCC 的错误消息特别有趣,暗示发明的类型模板参数被视为具体类型而不是泛型类型占位符.

问题:

  • 关于A1D1(分别是拒绝和接受)的情况,GCC 和 Clang 中哪个是正确的?GCC 和 Clang 拒绝上述案例 E2 是否正确?(工作草案的)哪个标准段落明确支持他们?

解决方案

这个:

<块引用>

template void e(T, auto);

翻译成:

template无效 e(T, U);

相比之下,这个:

<块引用>

template void e(auto, T) {}

翻译成:

template void e(U, T) {}

记住缩写的函数模板参数放在模板的末尾参数列表.因此,由于颠倒了模板参数的顺序,因此它们不会声明相同的模板.第一个声明一个模板,第二个声明并定义一个不同的模板.

您不会因此而得到编译错误,因为第二个定义也是一个声明.但是,当您使用类成员时,成员外定义不是声明.因此,它们必须具有匹配的成员内声明.他们没有;因此出现错误.


至于其他,功能等效(但不等效)"text 是一个非规范的符号.您引用的实际规范文本明确指出,它们是等效的",而不仅仅是功能等效的".由于术语等效",根据 [temp.over.link]/7,用于匹配声明和定义,在我看来,标准规定 A 到 D 的情况都可以.

奇怪的是,这个非规范性文本是 由引入规范文本的同一提案引入.但是,它从 ConceptName auto 语法的提议rel="noreferrer">似乎很清楚它意味着等效",而不是功能等效".

所以就规范文本而言,一切似乎都很清楚.但非规范性矛盾的存在表明规范存在编辑问题或实际缺陷.


虽然标准本身在措辞方面清晰且规范合理,但似乎这不是标准的作者打算.

P0717 介绍了这个概念的功能等效"区别于等效".该提议被接受了.但是,P0717 是在采用适用于 C++20 的概念 TS 的过程中早期引入的.在该提案中,它特别谈到了简洁的模板语法,并且 EWG 明确投票赞成采用功能等效"的模板语法.它的措辞而不是概念 TS等效";措辞.

也就是说,P0717 明确委员会打算要求用户使用一致的语法.

然而,Concepts TS 中的简洁模板语法已从 C++20 中删除(或者更确切地说,从未真正添加).这意味着任何功能等效"措辞从未出现过,因为该功能从未出现过.

然后发生了 P1141,它添加了缩写模板语法,涵盖了 Concepts TS 简洁模板语法的大部分内容.但是,尽管 P0717 的作者之一是 P1141 的作者,但显然有人在措辞上犯了错误,没有人发现.这将解释为什么非规范性文本指出缺乏真正的等效性:因为这实际上是委员会的意图.

所以这很可能是规范文本中的错误.

All standard references below refers to the current ISO Standard Working Draft, generated on 2020-06-22.


[dcl.fct]/18 states that [extract, emphasis mine]:

An abbreviated function template is a function declaration that has one or more generic parameter type placeholders ([dcl.spec.auto]). An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [...]

Such that the following to function declarations are areuably equivalent:

template <typename T>
void f(T);

void f(auto);  // re-declaration

We may note, however, that the example of [dcl.fct]/18 states that

[...]

These declarations are functionally equivalent (but not equivalent) to the following declarations.

[...]

which may arguably (I'm unsure how interpret this) conflict with the equivalence statement in the prior passage.

Now, both GCC 10.1.0 and Clang 10.0.0 (as well as GCC:HEAD and Clang:HEAD) have some mixed behavior here. If we declare a function template and later define it (/re-declare it) using a mixed classical function template syntax with abbreviated function template syntax, Clang accepts most cases (defining a previously declared function) whereas GCC rejects all (sees the (attempted) re-declarations as separately declared functions with subsequent ambiguity failures in overload resolution):

// A1: Clang OK, GCC error
template <typename T>
void a(T);

void a(auto) {}

// B1: Clang OK, GCC error
void b(auto);

template <typename T>
void b(T) {}

// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);

void c(auto, auto) {}

// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);

template <typename T>
void d(T, auto) {}

// E1: Clang error, GCC error
template <typename T>
void e(T, auto);

template <typename T>
void e(auto, T) {}

int main() {
    a(0);      // Clang OK, GCC error.
    b(0);      // Clang OK, GCC error.
    c(0, '0'); // Clang OK, GCC error.
    d(0, '0'); // Clang OK, GCC error.
    e(0, '0'); // Clang error, GCC error.
}

Curiously, if we make the function template a class member function template, both GCC and Clang accepts cases A1 through D1, but both rejects the final case E1 above:

// A2: OK
struct Sa {
    template <typename T>
    void a(T);
};

void Sa::a(auto) {}

// B2: OK
struct Sb {
    void b(auto);
};

template <typename T>
void Sb::b(T) {}

// C2: OK
struct Sc {
    template <typename T, typename U>
    void c(T, U);
};

void Sc::c(auto, auto) {}

// D2: OK
struct Sd {
    template <typename T, typename U>
    void d(T, U);
};

template <typename T>
void Sd::d(T, auto) {}

// E2: Error
struct Se {
   template <typename T>
   void e(T, auto);
};

template <typename T>
void Se::e(auto, T) {}

with the following error messages:

GCC

error: no declaration matches 'void Se::e(auto:7, T)'

note: candidate is: 
  'template<class T, class auto:6> void Se::e(T, auto:6)'

Clang

error: out-of-line definition of 'e' does not match 
any declaration in 'Se'

Now, the name of a type template parameter is not required to be consistent over re-declaration (or a definition) of a function template, as just names a generic type placeholder.

GCC's error message is particularly interesting, hinting that invented type template parameters are treated as concrete types rather than generic type placeholders.

Question:

  • Which of GCC and Clang are correct regarding cases A1 through D1 (rejecting and accepting, respectively)? Are GCC and Clang correct to reject case E2 above? What standard passage (of the working draft) unambiguously supports them?

解决方案

This:

template <typename T>
void e(T, auto);

Translates to this:

template<typename T, typename U>
void e(T, U);

By contrast, this:

template <typename T>
void e(auto, T) {}

translates to:

template <typename T, typename U>
void e(U, T) {}

Remember that abbreviated function template parameters are placed at the end of the template parameter list. So those aren't declaring the same template, due to reversing the order of the template parameters. The first one declares one template, the second one declares and defines a different template.

You don't get a compile error just from this because the second definition is also a declaration. However, when you use a class member, out-of-member definitions are not declarations. Therefore, they must have a matching in-member declaration. Which they don't; hence the errors.


As for the others, the "functionally equivalent (but not equivalent)" text is a non-normative notation. The actual normative text that you quoted clearly states that these are "equivalent", not merely "functionally equivalent". And since the term "equivalent", per [temp.over.link]/7, is used to match declarations and definitions, it seems to me that the standard states that the A through D cases are fine.

What's weird is that this non-normative text was introduced by the same proposal that introduced the normative text. However, the proposal that it inherited the ConceptName auto syntax from seems clear that it means "equivalent", not "functionally equivalent".

So in terms of normative text, everything seems clear. But the presence of the non-normative contradiction suggests either an editorial problem or an actual defect in the spec.


While the standard itself is clear and normatively reasonable in terms of wording, it appears that this is not what the writers of the standard intended.

P0717 introduced the concept of "functionally equivalent" as being distinct from "equivalent". And that proposal was accepted. However, P0717 was introduced early in the process of adopting the Concepts TS for C++20. In that proposal, it specifically spoke of terse template syntax, and EWG explicitly voted in favor of adopting "functionally equivalent" wording for it instead of the Concepts TS "equivalent" wording.

That is, P0717 makes it clear that the committee intended for users to be required to use consistent syntax.

However, terse template syntax from Concepts TS was removed from C++20 (or rather, never really added). Which meant that any "functionally equivalent" wording never made it in, since the feature never made it in.

Then P1141 happened, which added abbreviated template syntax, that covered much of the ground of the Concepts TS terse template syntax. But, despite one of the authors of P0717 being an author of P1141, apparently someone made a mistake in the wording and nobody caught it. This would explain why the non-normative text calls out the lack of true equivalence: because that was actually the committee's intent.

So it's very possible that this is a mistake in the normative text.

这篇关于函数模板和缩写函数模板之间的等价性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆