gpt4 book ai didi

c++ - 为什么在其他函数中声明的函数不参与参数相关查找?

转载 作者:可可西里 更新时间:2023-11-01 18:24:20 31 4
gpt4 key购买 nike

考虑一个简单的例子:

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 {}; }

两者 [gcc][clang]拒绝编译代码。这段代码在某些方面格式不正确吗?

最佳答案

foo(tag<int>{});触发 foo 的函数调用运算符成员函数模板的特化的隐式实例化带有模板参数的闭包类型 tag<int> .这会为此成员函数模板特化创建一个实例化点。根据 [temp.point]/1:

For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.



(强调我的)

所以,实例化点紧跟在 main 之后的定义,在 bar 的命名空间范围定义之前.

名称查找 bar用于 decltype(bar(x))根据 [temp.dep.candidate]/1 进行:

For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules (6.4.1, 6.4.2) except that:

(1.1) — For the part of the lookup using unqualified name lookup (6.4.1), only function declarations from the template definition context are found.

(1.2) — For the part of the lookup using associated namespaces (6.4.2), only function declarations found in either the template definition context or the template instantiation context are found. [...]



定义上下文中的普通非限定查找找不到任何东西。定义上下文中的 ADL 也找不到任何东西。实例化上下文中的 ADL,根据 [temp.point]/7:

The instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.



同样,没什么,因为 bar尚未在命名空间范围内声明。

所以,编译器是正确的。此外,请注意 [temp.point]/8:

A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one-definition rule (6.2), the program is ill-formed, no diagnostic required.



(强调我的)

和 [temp.dep.candidate]/1 的第二部分:

[...] If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.



因此,格式错误的 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 指令。这将使 bar根据 [basic.lookup.argdep]/4,对普通的非限定查找可见,但对 ADL 不可见:

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (6.4.3.2) except that:

(4.1) — Any using-directives in the associated namespace are ignored. [...]



由于在实例化上下文中仅执行查找的 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:

Let X be the lookup set produced by unqualified lookup (6.4.1) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains

(3.1) — a declaration of a class member, or

(3.2) — a block-scope function declaration that is not a using-declaration, or

(3.3) — a declaration that is neither a function nor a function template

then Y is empty. [...]



(强调我的)

现在,问题是这是否在定义和实例化上下文中禁用 ADL,或者仅在定义上下文中禁用。

如果我们认为在这两种情况下 ADL 都被禁用,那么:
  • 块范围声明对定义上下文中的普通非限定查找可见,是唯一对闭包类型的成员函数模板特化的所有实例化可见的声明。 Clang 的错误消息,没有可行的转换为 int , 是正确且必需的 - 上面关于格式错误的 NDR 和未定义行为的两个引号不适用,因为在这种情况下实例化上下文不会影响名称查找的结果。
  • 即使我们搬家bar上面的命名空间范围定义 main ,代码仍然无法编译,原因与上述相同:当它找到块范围声明 void bar(int); 时,普通的非限定查找停止并且不执行 ADL。

  • 如果我们认为仅在定义上下文中禁用 ADL,则:
  • 就实例化上下文而言,我们回到第一个例子; ADL 仍然找不到 bar 的命名空间范围定义.然而,上面的两个引号(格式错误的 NDR 和 UB)确实适用,因此我们不能责怪编译器没有发出错误消息。
  • 搬家 bar上面的命名空间范围定义 main使代码格式良好。
  • 这也意味着实例化上下文中的 ADL 总是针对依赖名称执行,除非我们以某种方式确定表达式不是函数调用(通常涉及定义上下文......)。

  • 看 [temp.dep.candidate]/1 的措辞,似乎是说,plain unqualified lookup 只在定义上下文中作为第一步执行,然后根据 [basic.lookup.txt] 中的规则执行 ADL。 argdep] 在这两种情况下作为第二步。这意味着普通不合格查找的结果会影响整个第二步,这使我倾向于第一个选项。

    此外,支持第一个选项的更有力的论据是,当 [basic.lookup.argdep]/3.1 或 3.3 在定义上下文中应用时,在实例化上下文中执行 ADL 似乎没有意义。

    仍然......可能值得在 std-discussion上询问这个问题.

    所有报价均来自 N4713 ,当前的标准草案。

    关于c++ - 为什么在其他函数中声明的函数不参与参数相关查找?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48586355/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com