函数对象对于重载解析有何影响?

12

在重载解析期间,函数对象是否与常规函数处理不同?如果是,如何处理?

我遇到了以下情况,在这种情况下,将一个函数替换为等效可调用的函数对象会改变代码的含义:

#include <iostream>

namespace N
{
    enum E { A, B };

    void bar(E mode) { std::cout << "N::bar\n"; }
}

template <typename... Args>
void bar(Args&&... args) { std::cout << "global bar\n"; }

int main()
{
    bar(N::A);
}

这里的输出是"N::bar"。到目前为止,一切都很好:ADL找到了N::bar,全局的bar和N::bar都是完全匹配的,但由于它不是一个模板,所以N::bar被优先选择。

但是如果我将全局的bar改为函数对象,像这样:

#include <iostream>

namespace N
{
    enum E { A, B };

    void bar(E mode) { std::cout << "N::bar\n"; }
}

struct S
{
    template <typename... Args>
    void operator()(Args&&... args) { std::cout << "global bar\n"; }
};
S bar;

int main()
{
    bar(N::A);
}
输出现在是 "global bar",为什么会有这种差异?
2个回答

12
重要的一点是,只有在查找确定函数调用中的名称确实是一个函数时,ADL才会发挥作用。在第二个例子中,发现bar是一个对象而不是函数,因此表达式bar(N::A)不是函数调用,而是将operator()应用于对象bar。由于这不是函数调用,因此ADL不会发挥作用,也不会考虑N::bar
引用块中提到,在函数调用的后缀表达式中使用未限定名称的查找描述在3.4.2中。[注意:为了确定(在解析期间)表达式是否为函数调用的后缀表达式,通常的名称查找规则适用。 3.4.2中的规则[ADL]对表达式的语法解释没有影响。]
另一种看待它的方法是注意到ADL会将新函数添加到重载函数集合中,但在第二个例子中没有这样的集合:查找找到一个对象,调用对象的成员。

我认为3.4.1/3的注释并不相关,因为bar确实是函数调用表达式中的后缀表达式(没有函数作为操作数,但最终会调用一个函数!)。该注释说3.4.2不会改变“x(y)”的语法含义-如果它是函数调用,则无论3.4.2决定做什么,它仍然是函数调用。 - Johannes Schaub - litb
@JohannesSchaub-litb:常规查找将发现bar是一个对象,而作为对象的bar(x)表示对该实例应用operator()。虽然运算符的应用是函数调用,但它是一个成员函数调用,因此ADL不适用。 - David Rodríguez - dribeas
@david,你在混淆所有不同的实体。 - Johannes Schaub - litb

4
请看3.4.2p3,其内容如下:让X成为由未限定查找产生的查找集合(3.4.1),并让Y成为由参数依赖查找产生的查找集合(定义如下)。如果X包含以下内容:
- ... - 一个既不是函数也不是函数模板的声明
那么Y为空。
如果没有这样的规则,您就是正确的: ADL会将另一个函数添加到重载集中。事实上,13.3.1.1p1依赖于此:它有两个分支;一个用于函数调用表达式,其中操作数表示类对象,另一个用于操作数表示一个或多个函数或函数模板。

在审阅了标准的那部分内容后,你提供的引用正是我引述注释的原因。+1 - David Rodríguez - dribeas
@david 你误解了它。 - Johannes Schaub - litb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接