中缀与前缀语法:名称查找差异

Infix vs prefix syntax: name lookup differences

本文关键字:查找 前缀 语法 中缀      更新时间:2023-10-16

操作符在c++中通常被认为是函数/方法的替代语法,特别是在重载上下文中。如果是这样,下面的两个表达式应该是同义的:

std::cout << 42;
operator<<(std::cout, 42);
实际上,第二个语句会导致以下错误:
call of overloaded ‘operator<<(std::ostream&, int)’ is ambiguous

通常,这样的错误消息伴随着一个可能的候选列表,它们是:

operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
operator<<(basic_ostream<char, _Traits>& __out, char __c)
operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)

这样的错误至少引起两个问题:

  1. 这两个语句在哪些方面不同(在名称查找方面)?
  2. 为什么operator<<(basic_ostream<char, _Traits>& __out, int __c)缺失?

看来,中缀和前缀符号不能完全互换——不同的语法需要不同的名称解析策略。它们的区别是什么?它们从何而来?

不,这两个表达式不应该是同义词。std::cout << 42被查找为operator<<(std::cout, 42)std::cout.operator<<(42)。这两种查找都产生可行的候选项,但第二个查找更匹配。

这些是c++ 17 [over.match]中的操作符查找规则。在这里,为了简洁起见,我删除了与重载operator<<无关的文本,左操作数是一个类类型;并包含了一个部分,我将在后面解释:

对于二元操作符@,其左操作数的非限定版本为T1,右操作数的非限定版本为T2,有三组候选函数,指定为成员候选函数非成员函数候选项内置候选项的构造方法如下:

  • 如果T1是一个完整的类类型或一个正在定义的类,则候选成员集是T1::operator@(16.3.1.1.1)的限定查找的结果;否则,候选成员集为空。
  • 非成员候选者集合是根据非限定函数调用中名称查找的通常规则在表达式上下文中对operator@进行非限定查找的结果,只是所有成员函数都被忽略

内置候选者在这里为空,它指的是将隐式地将两个操作数转换为整数类型并应用位移位操作符的搜索函数;但是没有从iostreams到整型的隐式转换。

用于重载解析的候选函数集是成员候选函数、非成员候选函数和内置候选函数的并集。


操作符查找与其他函数查找的基本原理是什么?这意味着什么?我认为通过几个例子可以最好地回答这个问题。首先:

struct X{ operator int(); };
void f(X);
struct A
{
    void f(int);
    void g() { X x; f(x); }    // Calls A::f
};

在这个例子中有一个原则:如果你试图从类的另一个成员函数调用类的一个成员函数;它应该明确地找到成员函数,而不是让搜索被外部函数(甚至包括ADL)污染。

因此,非限定查找规则的一部分是,如果查找的非ADL部分找到一个类成员函数,则不执行ADL。

如果没有这个规则,f(x)会同时找到A::f::f,然后重载解析会选择::f作为更好的匹配,这是我们不希望的。

到第二个例子:

struct X{};
std::ostream& operator<<(std::ostream&, X);
struct S
{
    std::ostream& operator<<(int);
    void f()
    {
         X x;
         std::cout << x;   // OK
         // operator<<(std::cout, x);  // FAIL
         // std::cout.operator<<(x);   // FAIL
    }
};

按照前面示例的原则—如果规则只是将std::cout << 42;转换为operator<<(std::cout, 24);,则名称查找将找到S::operator<<并停止。哎呀!

所以我认为说上面OK行的行为来自标记为FAIL的两行是不太正确的,正如其他答案/评论所建议的那样。


简介:

现在我们可以理解我的答案顶部的标准报价的实际措辞了。

代码std::cout << x;将:

  • 查找std::cout.operator<<(x); AND
  • 查找operator<<(std::cout, x),除非成员函数被忽略(因此,由于成员函数被发现,没有adl抑制)。

则对这两个集合的并集执行重载解析。