【问题标题】:Forcing name lookup to consider namespace scope强制名称查找考虑命名空间范围
【发布时间】:2015-11-15 16:04:39
【问题描述】:

这个问题与point of instantiation and name binding 有点关系,但不完全是。问题是关于标准以及它如何解析模板定义中的符号查找。

考虑这个例子,松散地基于 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 节中查找说(略有编辑):

[...] 形式的表达式:

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

其中后缀表达式是一个标识符标识符表示一个依赖名当且仅当有expression-list 中的表达式是依赖于类型的表达式 (14.6.2.2)。

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

this 是类型相关的,如果封闭的成员函数的类类型是相关的

AIUI 就是这种情况。

所以,问题是:

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

更新:将帖子的标题更改为更准确,并对标准中的引用进行了轻微编辑。

【问题讨论】:

  • 因为如果找到成员函数(emit 是成员),ADL 就不会发生。在实例化点发生的唯一事情是 ADL 查找。让它找到一个非成员函数(查找“使用 std::swap;”成语)。 namespace adlenabler { void emit(); };(代表你的班级..我懒得展示:))而是写using adlenabler::emit; emit(*this, x);
  • 嗯?您知道所描述的标准在哪里吗?
  • 啊忘了我说的关于委派给成员的内容。无论如何,这不是故意的,而且它们的参数不足。好吧,我说的其他事情仍然适用:)
  • 我设法使用您的建议使其工作。我将Output 类和emit 函数放在单独的命名空间中,并将using 添加到模板成员函数中。如果您在此处添加答案,我会为您打勾。
  • 我会发布一个答案来澄清情况。仍然缺少使用声明的段落来解释它的工作原理。

标签: c++ templates name-lookup


【解决方案1】:

正如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:

出于重载决议的目的,由using-declaration引入派生类的函数将被视为派生类的成员。

但这指的是从B 派生的类D 中的using B::f,所以不是完美匹配。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多