考虑一个简单的例子:
template <class T>
struct tag { };
int main() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{}); // <- compiles OK
foo(tag<int>{}); // 'bar' was not declared in this scope ?!
}
tag<int> bar(tag<int>) { return {}; }
答案 0 :(得分:7)
foo(tag<int>{});
使用模板参数foo
触发tag<int>
闭包类型的函数调用操作符成员函数模板的特化的隐式实例化。这为此成员函数模板特化创建了实例化点。根据[temp.point] / 1:
对于功能模板专业化,成员函数模板 专业化,或成员函数或静态的专业化 如果专门化是隐式的,则为类模板的数据成员 实例化,因为它是从另一个模板中引用的 专业化和引用它的上下文取决于 模板参数,专业化的实例化点 是封闭专业化的实例化点。 否则,这种专业化的实例化点 紧跟在命名空间范围声明或定义之后 指的是专业化。
(强调我的)
因此,实例化的重点是在main
的定义之后,在bar
的命名空间范围定义之前。
bar
中使用的decltype(bar(x))
的名称查找根据[temp.dep.candidate] / 1进行:
对于 postfix-expression 是依赖项的函数调用 名称,使用通常的查找规则找到候选函数 (6.4.1,6.4.2)除了:
(1.1) - 对于使用不等式名称查找的查找部分 (6.4.1),只有模板定义中的函数声明 找到了背景。
(1.2) - 对于使用关联命名空间的查找部分 (6.4.2),只在模板中找到函数声明 找到定义上下文或模板实例化上下文。 [...]
定义上下文中的普通不合格查找并没有找到任何内容。定义上下文中的ADL也找不到任何东西。根据[temp.point] / 7:
,在实例化上下文中进行ADL依赖于表达式的表达式的实例化上下文 模板参数是具有外部链接的声明集 在模板实例化之前声明 在同一翻译单位专业化。
同样,没有,因为bar
尚未在命名空间范围内声明。
所以,编译器是正确的。此外,请注意[temp.point] / 8:
功能模板的专业化,成员函数模板, 或者类模板的成员函数或静态数据成员可以 在翻译单元中有多个实例化点,和 除了上面描述的实例化点之外,对于任何 这种专业化有一个实例化点 翻译单位,也考虑翻译单位的结尾 实例化点。类模板的特化有 翻译单元中最多一个实例化点。一个 任何模板的特化都可能有实例化点 多个翻译单位。 如果根据一个定义规则,两个不同的实例化点给出了模板特化的不同含义 (6.2),该程序格式错误,无需诊断。
(强调我的)
和[temp.dep.candidate] / 1的第二部分:
[...]如果电话会形成不良或者发现更好的比赛了 相关名称空间内的查找考虑了所有 在那些中引入了具有外部链接的函数声明 所有翻译单元中的名称空间,而不只是考虑那些 在模板定义和模板中找到的声明 实例化上下文,然后该程序具有未定义的行为。
因此,形成不良的NDR或未定义的行为,请选择。
让我们考虑上面your comment的例子:
template <class T>
struct tag { };
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
tag<int> bar(tag<int>) { return {}; }
int main() {
auto foo = build();
foo(tag<int>{});
}
定义上下文中的查找仍然找不到任何内容,但实例化上下文紧跟在main
的定义之后,因此该上下文中的ADL在全局中找到bar
命名空间(与tag<int>
关联)和代码编译。
让我们从上面的his comment开始考虑AndyG的例子:
template <class T>
struct tag { };
//namespace{
//tag<int> bar(tag<int>) { return {}; }
//}
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
namespace{
tag<int> bar(tag<int>) { return {}; }
}
int main() {
auto foo = build();
foo(tag<int>{});
}
同样,实例化点紧接在main
的定义之后,为什么不能bar
可见?未命名的命名空间定义在其封闭的命名空间(本例中为全局命名空间)中为该命名空间引入了 using-directive 。这会使bar
对于普通的非限定查找可见,但根据[basic.lookup.argdep] / 4不会对ADL显示:
在考虑关联的命名空间时,查找与...相同 当关联的命名空间用作a时执行的查找 资格(6.4.3.2)除外:
(4.1) - 关联命名空间中的任何 using-directives 都是 忽略。 [...]
由于在实例化上下文中仅执行查找的ADL部分,因此未命名的命名空间中的bar
不可见。
注释掉较低的定义并取消注释较低的定义,使得未命名的命名空间中的bar
在定义上下文中对于普通的非限定查找是可见的,因此代码会编译。
我们也考虑上面other comment中的示例:
template <class T>
struct tag { };
int main() {
void bar(int);
auto foo = [](auto x) -> decltype(bar(decltype(x){})) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{});
foo(tag<int>{});
}
tag<int> bar(tag<int>) { return {}; }
这被GCC接受,但被Clang拒绝。虽然我最初确信这是GCC中的一个错误,但答案可能实际上并不那么明确。
块范围声明void bar(int);
根据[basic.lookup.argdep] / 3禁用ADL:
设X是由unquali fi ed lookup(6.4.1)和let生成的查找集 Y是由参数依赖查找产生的查找集(定义为 如下图)。如果X包含
(3.1) - 类成员的声明,或
(3.2) - 不是a的块范围函数声明 使用声明或
(3.3) - 既不是函数也不是函数的声明 模板
然后Y为空。 [...]
(强调我的)
现在,问题是这是否在定义和实例化上下文中禁用ADL,或者仅在定义上下文中禁用ADL。
如果我们在两种情况下都考虑禁用ADL,那么:
int
,是正确且必需的 - 以上关于格式错误的NDR和未定义行为的两个引用不适用,因为在这种情况下,实例化上下文不会影响名称查找的结果。bar
的命名空间范围定义移到main
之上,代码仍然无法编译,原因与上述相同:简单的非限定查找在它找到块范围声明void bar(int);
并且不执行ADL。如果我们仅在定义上下文中考虑禁用ADL,则:
bar
的命名空间范围定义。上面的两个引号(格式错误的NDR和UB)确实适用,因此我们不能责怪编译器没有发出错误消息。bar
的命名空间范围定义移到main
之上会使代码格式正确。看看[temp.dep.candidate] / 1是如何措辞的,似乎只是在定义上下文中执行普通的非限定查找作为第一步,然后根据[basic]中的规则执行ADL .lookup.argdep]在两种情况下都是第二步。这意味着普通不合格查找的结果会影响整个第二步,这使我倾向于第一种选择。
另外,支持第一个选项的更强有力的论据是,当在定义上下文中应用[basic.lookup.argdep] /3.1或3.3时,在实例化上下文中执行ADL似乎没有意义。
仍然......可能值得在std-discussion上询问这个问题。
所有报价均来自N4713,即当前的标准草案。
答案 1 :(得分:5)
来自不合格的查找规则([basic.lookup.unqual]):
对于类
的定义HtmlMeta tag = new HtmlMeta(); tag.HttpEquiv = "X-UA-Compatible"; tag.Content = "IE=edge,chrome=1"; Header.Controls.AddAt(0, tag);'
的成员,成员函数体中使用的名称[...]应在其中一个中声明。 以下方式
- 如果X
是本地类或是本地类的嵌套类,则在块中定义类X
之前 包含类X
您的通用lambda是X
中的本地类,因此要使用main
,名称bar
必须事先出现在声明中。