为什么在其他函数中声明的函数不参与参数依赖查找?

时间:2018-02-02 15:55:08

标签: c++ templates c++14 language-lawyer argument-dependent-lookup

考虑一个简单的例子:

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]都拒绝编译代码。这段代码是否以某种方式形成错误?

2 个答案:

答案 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,那么:

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

如果我们仅在定义上下文中考虑禁用ADL,则:

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

看看[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必须事先出现在声明中。