首页 > 解决方案 > 两阶段功能模板编译:不是*仅* ADL 在第二阶段使用?

问题描述

我想知道为什么下面的代码会编译。

#include <iostream>

template<class T> 
void print(T t) {
    std::cout << t;
}

namespace ns {
    struct A {};
}

std::ostream& operator<<(std::ostream& out, ns::A) {
    return out << "hi!";
}

int main() {
    print(ns::A{}); 
}

我的印象是,在实例化点,仅通过 ADL 查找不合格的依赖名称- 这不应该考虑全局命名空间。我错了吗?

标签: c++templateslanguage-lawyerargument-dependent-lookupname-lookup

解决方案


这是一个有趣的案例。您描述的名称查找的工作原理总结如下:

[temp.dep.candidate](强调我的)

1对于后缀表达式是从属名称的函数调用,使用通常的查找规则([basic.lookup.unqual]、[basic.lookup.argdep])找到候选函数,除了:

  • 对于使用非限定名称查找的查找部分,仅找到来自模板定义上下文的函数声明。

  • 对于使用关联命名空间 ([basic.lookup.argdep]) 的查找部分,只能找到在模板定义上下文或模板实例化上下文中找到的函数声明。

如果调用格式错误或找到更好的匹配,则在关联命名空间中的查找考虑所有翻译单元中这些命名空间中引入的具有外部链接的所有函数声明,而不仅仅是考虑在模板定义和模板中找到的那些声明实例化上下文,则程序具有未定义的行为。

我强调的一点是问题的症结所在。“仅限 ADL”的描述是针对 from 的函数调用foo(bar)!它没有提到重载运算符导致的调用。我们知道调用重载的运算符等同于调用函数,但该段谈到了特定形式的表达式,仅是函数调用。

如果要将您的功能模板更改为

template<class T> 
void print(T t) {
    return operator<< (std::cout, t);
}

现在通过后缀表达式表示法调用一个函数,然后你瞧:GCC 会发出一个与 Clang 等效的错误。它可靠地实现了上面的段落,只是在涉及重载的运算符调用时不是。

那么它是一个错误吗?我会说是的。目的肯定是像命名函数一样找到重载运算符(即使从它们各自的表达式形式调用时)。所以GCC需要修复。但该标准也可以对措辞进行细微的澄清。


推荐阅读