为何依赖名的非限定名查找无法找到模板声明与实例化间的lambda?
以下程序无法通过编译:
inline constexpr auto makeFoo = [](auto const& x) { return getFoo(x); }; inline constexpr auto getFoo = [](int const&) { return 1; }; int bar() { return makeFoo(32); }
交换makeFoo与getFoo的定义顺序后,程序即可正常运行。
基于对两阶段名字查找的“非正式”理解,提问者有如下认知:
makeFoo是一个泛型lambda,其体内调用getFoo时,实参类型尚未确定,因此无法立即查找getFoo这个名字;- 当遇到调用
makeFoo(32)时,lambda的operator()会以auto = int为参数进行实例化,此时会对非限定名getFoo进行第二阶段查找; - 但该阶段仅进行参数依赖查找(ADL),无法找到
getFoo,因为它是lambda(即对象)而非函数的名字。
问题
能否找到C++标准草案中与上述解释对应的条款?
提问者猜测[basic.lookup.unqual]和[temp.dep.res]可能包含所需内容,但无法将这些条款串联起来。
对应标准条款解析
1. 泛型lambda的本质:模板成员函数
泛型lambda的调用运算符operator()是一个函数模板(出自[expr.prim.lambda.generic]条款),因此它的名字查找完全遵循模板的两阶段查找规则,核心对应[temp.dep.res](模板依赖名称的解析)和[basic.lookup.unqual](非限定名查找)。
2. 第一阶段:模板定义点的查找
根据[temp.dep.res]第1段:
在模板定义时,会查找不依赖于模板参数的名称;对于依赖于模板参数的名称,仅检查是否存在合法声明(若为函数调用则检查重载可行性),具体绑定延迟到实例化阶段。
在代码中,getFoo(x)的调用本质是调用getFoo.operator()(x),此时getFoo这个名字的查找不依赖模板参数x的类型——无论x是什么类型,我们要找的都是名为getFoo的对象。因此getFoo属于非依赖名称,必须在模板定义点(也就是makeFoo的定义位置)完成查找。
原代码中getFoo在makeFoo之后定义,第一阶段查找找不到这个名字,直接触发编译错误。
3. 第二阶段:实例化点的查找与ADL的限制
根据[basic.lookup.unqual]第6段:
对于模板实例化中的非限定名,只有当该名称是依赖于模板参数的,才会在实例化点进行补充查找,同时结合参数依赖查找(ADL);非依赖名称仅在模板定义点查找,不会在实例化点重新查找。
回到场景中,getFoo是非依赖名称,因此不会进入第二阶段查找,自然也不会触发ADL。另外,ADL本身的作用范围仅针对普通函数/函数模板的名称查找(出自[basic.lookup.argdep]),它不会查找对象名称——哪怕getFoo是一个可调用对象,ADL也不会帮你找到它。
4. 交换定义顺序为何生效?
当把getFoo的定义放在makeFoo之前时,makeFoo定义点已经存在getFoo的声明,第一阶段查找成功找到该对象,后续实例化时就能正常调用其operator()。
内容的提问来源于stack exchange,提问作者Enlico

