强制名称查找以考虑命名空间范围

Forcing name lookup to consider namespace scope

本文关键字:命名空间 范围 查找      更新时间:2023-10-16

这个问题在一定程度上与实例化点和名称绑定有关,但并不完全相关。问题是关于标准及其如何解决在模板定义中查找符号的问题。

考虑这个例子,松散地基于ostream库:

// Output module
class Output {
 public:
  void operator<<(int);
  void operator<<(double);
  ...
};
// Item module
class Item {
  friend void operator<<(Output& obj, const Item& x) {
     ...
  }
};
// Main program
int main() {
  Output out;
  Item item;
  out << 3;
  out << 2.0;
  out << item;
}

在本例中,关键点是在使用输出模块的任何模块之前定义输出模块,并且有一个模块(Item模块)使用输出模块来发出项。

这允许在Output类内部定义基本的emit运算符,但任何定义新类并希望提供emit方法的模块都可以通过提供带有两个参数的友元函数来实现。到目前为止一切都很好。

现在,让我们尝试在不重载运算符的情况下使用相同的思想,而是对基本类型的预定义发射函数使用计划成员函数,并且仍然允许将特定于类的发射函数定义为类的友元函数:

class Output {
 public:
  template <class Type>
  void emit(Type x) {
    emit(*this, x);
  }
  void emit(int);
  void emit(double);
};
class Item {
  friend void emit(Output& obj, const Item& x) {
    ...
  }
  ...
};
int main() {
  Output out;
  Item item;
  out.emit(3);
  out.emit(2.0);
  out.emit(item);
}

与前面的代码相比,添加了一个模板函数,因为不需要根据类型有不同的调用约定。换句话说,应该可以使用约定out.emit(...),而不管发射的是什么项。

然而,当编译它(使用GCC 4.8.4)时,我们会得到以下错误:

example.cc: In instantiation of ‘void Output::emit(Type) [with Type = Item]’:
example.cc:49:20:   required from here
example.cc:33:9: error: no matching function for call to ‘Output::emit(Output&, Item&)’
         emit(*this, x);
         ^
example.cc:33:9: note: candidates are:
example.cc:32:12: note: template<class Type> void Output::emit(Type)
       void emit(Type x) {
            ^
example.cc:32:12: note:   template argument deduction/substitution failed:
example.cc:33:9: note:   candidate expects 1 argument, 2 provided
         emit(*this, x);
         ^
example.cc:36:12: note: void Output::emit(int)
       void emit(int) {
            ^
example.cc:36:12: note:   candidate expects 1 argument, 2 provided
example.cc:37:12: note: void Output::emit(double)
       void emit(double) {
            ^
example.cc:37:12: note:   candidate expects 1 argument, 2 provided

换句话说,在解析名称时,从不考虑顶级emit函数,而只考虑Output类内的成员函数。

我认为这是因为符号emit不是一个从属名称,因此是在(模板的)定义点而不是实例化点查找的,但C++标准中的14.6.2§1节说(稍微编辑一下):

[…]在形式的表达式中:

 nbsp nbsp后缀表达式(表达式列表opt)

其中后缀表达式标识符标识表示依赖名称当且仅当表达式列表中的任何表达式是类型依赖表达式(14.6.2.2)

此外,在14.6.2.2("类型相关表达式")§2中,它说:

如果封闭成员函数的类类型是依赖的,则this是依赖类型的

AIUI就是这样。

因此,问题是:

  1. 为什么在这里的名称解析中查找不考虑emit的顶级版本
  2. 是否可以使第二个示例以与第一个示例相同的方式工作,以便模板成员函数定义在实例化时同时考虑成员函数或命名空间范围函数

更新:更改了帖子的标题,使其更加准确,并对标准中的引号进行了轻微编辑。

Johannes指出,如果发现成员函数,则不调用ADL,并且需要ADL来找到Item的友元声明。

因此,为了回答第一个问题,C++标准(C++03版本)中的第3.4.2节§2说:

如果名称的普通非限定查找找到类成员函数的声明,则不考虑关联的命名空间和类。[…]

为了回答第二个问题,这里是固定的示例代码:

namespace emitter {
class Output;
template <class Type>
void emit(Output& out, const Type& t) {
  emit(out, t);
}
class Output {
 public:
  template <class Type>
  void emit(Type x) {
    using emitter::emit;
    emit(*this, x);
  }
  void emit(int);
  void emit(double);
};
}
class Item {
  friend void emit(emitter::Output& obj, const Item& x) {
    ...
  }
};

然而,我仍在努力寻找澄清using宣言解决问题的原因的段落。我现在能找到的最接近的是7.3.3§13:

为了解决重载问题,使用声明引入派生类的函数将被视为派生类的成员。

但这是指从B派生的D类中的using B::f,因此不是完全匹配。