Fed*_*dor 9 c++ lambda language-lawyer default-arguments c++20
由于 C++ 中的每个 lambda 函数都有自己的类型,并且 lambda 函数的类型可以用作模板参数的默认值,因此知道该类型在模板中的哪个时刻被替换是很有趣的。考虑一个 C++20 程序示例:
#include <concepts>
template<typename T = decltype([]{})>
using X = T;
template<typename T = X<>>
auto foo() { return T{}; }
int main() {
using T1 = decltype(foo());
using T2 = decltype(foo());
static_assert(!std::same_as<T1, T2>);
}
Run Code Online (Sandbox Code Playgroud)
这里类型别名定义中模板参数的默认类型X是 lambda 函数的类型 decltype([]{})。模板函数调用foo()返回该类型的值。
如果foo()程序中每次都出现新 lambda 类型的替换,则类型T1和T2必须是不同的,因为程序在static_assert. 并且此检查仅在 GCC 中通过。
在 Clang 和 MSVC 中,类型T1和T2是相同的,这意味着默认类型的替换不是每次都发生(而是在模板声明期间发生一次)。演示:https : //gcc.godbolt.org/z/dzYjf9r3q
根据标准,哪个编译器就在这里?
简短的回答是,这是标准中未指定的区域。默认模板参数被简单地假设为具有一个“值”,但不清楚这是否是单个值,或者每次使用默认模板参数是否意味着生成该值的新实例。
\n虽然标准不清楚您的断言是否应该通过,但默认模板参数中的 lambda 表达式是等待发生的 ODR 枪。详细内容如下所述。
\n为了剖析这一点,让我们从调用站点开始,即decltype(foo()). 这些使用函数模板参数推导,所以会发生什么:
\n\n当所有模板实参都已从默认模板实参推导或获取时,模板的模板参数列表中所有使用的模板参数都将替换为相应的推导或默认实参值。
\n
- [temp.deduct.general] \xc2\xa75
\n这意味着decltype(foo())被翻译成decltype(foo<X<>>()). 这是歧义的要点,因为没有说明“值”是指类型X<>还是指组成 的符号X<>。即,尚不清楚编译器是否允许执行像decltype(foo<__X_instantiation>()).
现在我们必须处理X<>,它是一个simple-template-id:
\n\n当simple-template-id未命名函数时,当需要默认参数的值时,会隐式实例化默认模板参数。
\n[实施例7:
\nRun Code Online (Sandbox Code Playgroud)\ntemplate<typename T, typename U = int> struct S { };\nS<bool>* p; // the type of p is S<bool, int>*\nU 的默认参数被实例化以形成类型
\nS<bool, int>*。- 结束示例]
- [temp.arg.general] \xc2\xa79
\n这意味着我们形成表达式decltype(foo<X<decltype([]{})>>),其中 的模板参数foo可能已被记忆。至于在哪里[]{}声明:
\n\n\n
\n- lambda 表达式的类型(也是闭包对象的类型)是唯一的、未命名的非联合类类型,称为闭包类型,其属性如下所述。
\n- 闭包类型在包含相应 lambda 表达式的最小块作用域、类作用域或命名空间作用域中声明。
\n
- [expr.prim.lambda.closure] \xc2\xa71, \xc2\xa72
\n如果允许编译器执行 的记忆化X<>,那么我们会在后面看到以下代码:
struct closure { /* ... */ };\n\n// The expression in decltype is not type-dependent, so decltype(closure{})\n// would NOT denote a new unique type.\n// The same applies to all uses of decltype in this problem.\ntemplate<typename T = decltype(closure{})>\nusing X = T;\n\nusing __X_instantiation = X<decltype(closure{})>; // = closure\n\ntemplate<typename T = __X_instantiation>\nauto foo() { return T{}; }\n\nint main() {\n using T1 = decltype(foo<decltype(__X_instantiation{})>()); // = closure\n using T2 = decltype(foo<decltype(__X_instantiation{})>()); // = closure\n /* ... */\n}\n\ntemplate auto foo<__X_instantiation>() { return __X_instantiation{}; }\nRun Code Online (Sandbox Code Playgroud)\n否则,我们最终会得到:
\n/* ... */\n\nint main() {\n struct closure1 { /* ... */ };\n using T1 = decltype(foo<X<decltype(closure1{})>>()); // = closure1\n struct closure2 { /* ... */ };\n using T1 = decltype(foo<X<decltype(closure2{})>>()); // = closure2\n /* ... */\n}\n\ntemplate auto foo<X<closure1>>() { return closure1{}; }\ntemplate auto foo<X<closure2>>() { return closure2{}; }\nRun Code Online (Sandbox Code Playgroud)\n目前尚不清楚哪一个是正确的,并且取决于实现是否选择 的记忆化X<>。不同编译器的实现方式不同,这就是为什么您static_assert(!std::same_as<T1, T2>)可以通过某些编译器,但不能通过其他编译器。
请注意,这是一个巨大的脚枪,因为如果您包含在多个翻译单元中,X它的格式会不正确:X
template<typename T = decltype([]{})>\nusing X = T;\nRun Code Online (Sandbox Code Playgroud)\n在[basic.def.odr]中,我们看到了灾难的展开:
\n\n\n[...](隐式或显式) template-id 或 simple-template-id 使用的默认模板参数被视为其标记序列存在于 [a template] 的定义中
\nD;也就是说,默认参数或默认模板参数遵守本段中描述的要求(递归地)。
- [basic.def.odr] \xc2\xa714.11
\n这意味着,当我们使用 时X<>,就好像我们写了:
template<typename T = __default_arg>\nusing X = T, /* and declare */ __default_arg = decltype([]{});\nRun Code Online (Sandbox Code Playgroud)\n注意:显然这不是合法的 C++,它只是为了说明默认模板参数被认为用于 ODR 目的。
\n\n\n\n如果
\nD是一个模板并且在多个翻译单元中定义,[...]\n这些要求也适用于([...])的每个定义中DD定义的相应实体。\n对于每个此类实体及其自身,行为就好像有一个具有单一定义的实体,包括将这些要求应用于其他实体。
由于默认参数被视为位于 内部X,因此这些 ODR 限制适用,并且其行为就好像有一个 的定义decltype([]{})。然而,因为我们为每个 lambda 表达式声明了一个新的闭包类型,所以 ODR 违规即将发生:
\n\n\nRun Code Online (Sandbox Code Playgroud)\n/* [...] */\ninline void g(bool cond, void (*p)() = []{}) {\n if (cond) g(false);\n}\n/* [... ] */\n[...]如果 的定义
\ng出现在多个翻译单元中,则程序格式错误(无需诊断),因为每个此类定义都使用引用不同 lambda 表达式闭包类型的默认参数。[...]
上述示例同样适用于decltype([]{})模板参数,因为与单一定义规则相关的规则相同。
注意:这些规则都不能帮助我们消除问题代码的歧义,因为它们仅适用于模板出现在多个翻译单元中的情况。
\n| 归档时间: |
|
| 查看次数: |
105 次 |
| 最近记录: |