dfr*_*fri 8 c++ templates language-lawyer c++20
以下所有标准参考均指2020 年 6 月 22 日生成的当前 ISO 标准工作草案。
[dcl.fct]/18指出[摘录,强调我的]:
的缩写函数模板是一个函数声明具有一个或多个通用参数类型占位符([dcl.spec.auto])。一个缩写的函数模板等价于一个函数模板([temp.fct]),它的模板参数列表包括一个发明的类型模板参数,用于函数声明的每个泛型参数类型占位符,按出现顺序。[...]
这样,函数声明的以下内容可能是等效的:
template <typename T>
void f(T);
void f(auto); // re-declaration
Run Code Online (Sandbox Code Playgroud)
然而,我们可能会注意到,[dcl.fct]/18的例子表明
[...]
这些声明在功能上与以下声明等效(但不等效)。
[...]
这可能(我不确定如何解释)可能与前一段中的等价陈述相冲突。
现在,GCC 10.1.0 和 Clang 10.0.0(以及 GCC:HEAD 和 Clang:HEAD)在这里都有一些混合行为。如果我们声明一个函数模板,然后使用混合经典函数模板语法和缩写函数模板语法定义它(/重新声明它),Clang 接受大多数情况(定义一个先前声明的函数),而 GCC 拒绝所有(见(尝试) ) 重新声明为单独声明的函数,随后在重载解析中出现歧义失败):
// 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.
}
Run Code Online (Sandbox Code Playgroud)
奇怪的是,如果我们让函数模板成为类成员函数模板,GCC 和 Clang 都接受A1到D1 的情况,但都拒绝上面的最后一个情况E1:
// 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) {}
Run Code Online (Sandbox Code Playgroud)
带有以下错误消息:
海湾合作委员会
Run Code Online (Sandbox Code Playgroud)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)'铛
Run Code Online (Sandbox Code Playgroud)error: out-of-line definition of 'e' does not match any declaration in 'Se'
现在,类型模板参数的名称不需要与函数模板的重新声明(或定义)保持一致,因为只需命名一个泛型类型占位符。
GCC 的错误消息特别有趣,暗示发明的类型模板参数被视为具体类型而不是泛型类型占位符。
这个:
Run Code Online (Sandbox Code Playgroud)template <typename T> void e(T, auto);
翻译成这样:
template<typename T, typename U>
void e(T, U);
Run Code Online (Sandbox Code Playgroud)
相比之下,这个:
Run Code Online (Sandbox Code Playgroud)template <typename T> void e(auto, T) {}
翻译成:
template <typename T, typename U>
void e(U, T) {}
Run Code Online (Sandbox Code Playgroud)
请记住,缩写的函数模板参数放在模板参数列表的末尾。因此,由于颠倒了模板参数的顺序,因此它们不会声明相同的模板。第一个声明一个模板,第二个声明并定义一个不同的模板。
您不会因此而得到编译错误,因为第二个定义也是一个声明。但是,当您使用类成员时,成员外定义不是声明。因此,它们必须具有匹配的成员内声明。他们没有;因此错误。
至于其他,“功能等效(但不等效)”文本是非规范性符号。您引用的实际规范文本明确指出这些是“等效的”,而不仅仅是“功能等效的”。由于[temp.over.link]/7术语“等效”用于匹配声明和定义,在我看来,标准指出 A 到 D 的情况都很好。
奇怪的是,这个非规范性文本是由引入规范性文本的同一个提案引入的。然而,它继承ConceptName auto语法的提议似乎很清楚,它的意思是“等效”,而不是“功能等效”。
因此,就规范文本而言,一切似乎都很清楚。但是非规范性矛盾的存在表明规范中存在编辑问题或实际缺陷。
虽然标准本身在措辞方面清晰且规范合理,但这似乎不是标准作者的意图。
P0717引入了与“等效”不同的“功能等效”的概念。该提议被接受了。但是,P0717 是在采用适用于 C++20 的概念 TS 的过程中早期引入的。在该提案中,它特别提到了简洁的模板语法,并且 EWG 明确投票赞成采用“功能等效”的措辞,而不是 Concepts TS 的“等效”措辞。
也就是说,P0717 明确委员会旨在要求用户使用一致的语法。
但是,Concepts TS 中的简洁模板语法已从 C++20 中删除(或者更确切地说,从未真正添加)。这意味着任何“功能等效”的措辞从未出现过,因为该功能从未出现过。
然后发生了 P1141,它添加了缩写模板语法,涵盖了 Concepts TS 简洁模板语法的大部分内容。但是,尽管 P0717 的作者之一是 P1141 的作者,但显然有人在措辞上犯了错误,没有人发现。这将解释为什么非规范性文本指出缺乏真正的等效性:因为这实际上是委员会的意图。
所以这很可能是规范文本中的一个错误。