函数模板与缩略函数模板的等价性
Equivalence between function templates and abbreviated function templates
以下所有标准参考均参考 current ISO Standard Working Draft,生成于 2020-06-22。
[dcl.fct]/18 指出[摘录,强调 我的]:
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. [...]
使得以下函数声明可以等价:
template <typename T>
void f(T);
void f(auto); // re-declaration
但是,我们可能会注意到,[dcl.fct]/18 的示例表明
[...]
These declarations are functionally equivalent (but not equivalent) to the following declarations.
[...]
这可以说(我不确定如何解释)与前面段落中的等价语句冲突。
现在,GCC 10.1.0 和 Clang 10.0.0(以及 GCC:HEAD 和 Clang:HEAD)在这里有一些混合行为。如果我们声明一个函数模板,然后使用混合的 classical 函数模板语法和缩写函数模板语法定义它(/重新声明它),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.
}
奇怪的是,如果我们将函数模板设为 class 成员函数模板,GCC 和 Clang 都接受 A1 到 D1[=59 的情况=],但都拒绝上面的最终案例 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) {}
出现以下错误消息:
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'
现在,类型模板参数的名称不需要与函数模板的重新声明(或定义)保持一致,因为只是命名泛型占位符。
GCC 的错误消息特别有趣,暗示发明的类型模板参数被视为具体类型而不是通用类型占位符。
问题:
- 关于案例 A1 到 D1(分别是拒绝和接受),GCC 和 Clang 中哪个是正确的? GCC 和 Clang 是否正确拒绝上述案例 E2?哪些(工作草案的)标准段落明确支持它们?
这个:
template <typename T>
void e(T, auto);
翻译为:
template<typename T, typename U>
void e(T, U);
相比之下,这个:
template <typename T>
void e(auto, T) {}
转换为:
template <typename T, typename U>
void e(U, T) {}
记住abbreviated function template parameters are placed at the end of the template parameter list。因此,由于颠倒了模板参数的顺序,它们没有声明相同的模板。第一个声明一个模板,第二个声明并定义一个不同的模板。
你不会因此而得到编译错误,因为第二个定义也是一个声明。但是,当您使用 class 成员时,out-of-member 定义是 而不是声明 。因此,它们必须具有匹配的 in-member 声明。他们没有;因此出现错误。
至于其他,“功能等同(但不等同)”文本是 non-normative 表示法。您引用的实际规范文本明确指出这些是“等效的”,而不仅仅是“功能等效”。由于术语“等效”,根据 [temp.over.link]/7,用于匹配声明和定义,在我看来,标准规定 A 到 D 情况都很好。
奇怪的是这个 non-normative 文本是 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"。
所以就规范性文本而言,一切似乎都很清楚。但是 non-normative 矛盾的存在表明规范中存在编辑问题或实际缺陷。
虽然标准本身在措辞上是明确且规范合理的,但这似乎不是标准编写者的意图。
P0717 引入了“功能等效”的概念,以区别于“等效”。那个提议被接受了。但是,P0717 是在采用 Concepts TS for C++20 的过程中早期引入的。在该提案中,它特别谈到了简洁的模板语法,EWG 明确投票赞成采用“功能等效”的措辞,而不是 Concepts TS 的“等效”措辞。
也就是说,P0717 明确表示委员会打算要求用户使用一致的语法。
然而,来自 Concepts TS 的简洁模板语法已从 C++20 中删除(或者更确切地说,从未真正添加)。这意味着任何“功能等效”的措辞都不会出现,因为该功能从未出现过。
然后 P1141 发生了,它添加了缩写模板语法,涵盖了 Concepts TS 简洁模板语法的大部分内容。但是,尽管 P0717 的作者之一是 P1141 的作者,但显然有人在措辞上犯了错误,但没有人发现。这可以解释为什么 non-normative 文本指出缺乏真正的对等:因为这实际上是委员会的意图。
所以这很可能是规范文本中的一个错误。
以下所有标准参考均参考 current ISO Standard Working Draft,生成于 2020-06-22。
[dcl.fct]/18 指出[摘录,强调 我的]:
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. [...]
使得以下函数声明可以等价:
template <typename T>
void f(T);
void f(auto); // re-declaration
但是,我们可能会注意到,[dcl.fct]/18 的示例表明
[...]
These declarations are functionally equivalent (but not equivalent) to the following declarations.
[...]
这可以说(我不确定如何解释)与前面段落中的等价语句冲突。
现在,GCC 10.1.0 和 Clang 10.0.0(以及 GCC:HEAD 和 Clang:HEAD)在这里有一些混合行为。如果我们声明一个函数模板,然后使用混合的 classical 函数模板语法和缩写函数模板语法定义它(/重新声明它),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.
}
奇怪的是,如果我们将函数模板设为 class 成员函数模板,GCC 和 Clang 都接受 A1 到 D1[=59 的情况=],但都拒绝上面的最终案例 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) {}
出现以下错误消息:
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'
现在,类型模板参数的名称不需要与函数模板的重新声明(或定义)保持一致,因为只是命名泛型占位符。
GCC 的错误消息特别有趣,暗示发明的类型模板参数被视为具体类型而不是通用类型占位符。
问题:
- 关于案例 A1 到 D1(分别是拒绝和接受),GCC 和 Clang 中哪个是正确的? GCC 和 Clang 是否正确拒绝上述案例 E2?哪些(工作草案的)标准段落明确支持它们?
这个:
template <typename T> void e(T, auto);
翻译为:
template<typename T, typename U>
void e(T, U);
相比之下,这个:
template <typename T> void e(auto, T) {}
转换为:
template <typename T, typename U>
void e(U, T) {}
记住abbreviated function template parameters are placed at the end of the template parameter list。因此,由于颠倒了模板参数的顺序,它们没有声明相同的模板。第一个声明一个模板,第二个声明并定义一个不同的模板。
你不会因此而得到编译错误,因为第二个定义也是一个声明。但是,当您使用 class 成员时,out-of-member 定义是 而不是声明 。因此,它们必须具有匹配的 in-member 声明。他们没有;因此出现错误。
至于其他,“功能等同(但不等同)”文本是 non-normative 表示法。您引用的实际规范文本明确指出这些是“等效的”,而不仅仅是“功能等效”。由于术语“等效”,根据 [temp.over.link]/7,用于匹配声明和定义,在我看来,标准规定 A 到 D 情况都很好。
奇怪的是这个 non-normative 文本是 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"。
所以就规范性文本而言,一切似乎都很清楚。但是 non-normative 矛盾的存在表明规范中存在编辑问题或实际缺陷。
虽然标准本身在措辞上是明确且规范合理的,但这似乎不是标准编写者的意图。
P0717 引入了“功能等效”的概念,以区别于“等效”。那个提议被接受了。但是,P0717 是在采用 Concepts TS for C++20 的过程中早期引入的。在该提案中,它特别谈到了简洁的模板语法,EWG 明确投票赞成采用“功能等效”的措辞,而不是 Concepts TS 的“等效”措辞。
也就是说,P0717 明确表示委员会打算要求用户使用一致的语法。
然而,来自 Concepts TS 的简洁模板语法已从 C++20 中删除(或者更确切地说,从未真正添加)。这意味着任何“功能等效”的措辞都不会出现,因为该功能从未出现过。
然后 P1141 发生了,它添加了缩写模板语法,涵盖了 Concepts TS 简洁模板语法的大部分内容。但是,尽管 P0717 的作者之一是 P1141 的作者,但显然有人在措辞上犯了错误,但没有人发现。这可以解释为什么 non-normative 文本指出缺乏真正的对等:因为这实际上是委员会的意图。
所以这很可能是规范文本中的一个错误。