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

时间:2015-11-15 16:04:40

标签: c++ templates name-lookup

这个问题与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方法的模块都可以通过提供具有两个参数的友元函数来实现。到目前为止一切都很好。

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

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不是依赖名称,因此被查找(模板的)定义点而不是实例化点,但是第14.6.2节§1中的C ++标准说(稍加编辑):

  

[...]表达形式:

     

postfix-expression ( 表达式列表 opt )

     

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

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

  如果封闭成员函数的类类型依赖

,则

this依赖于类型

其中,AIUI就是这种情况。

所以,问题是:

  1. 为什么查找不会在名称解析中考虑emit的顶级版本?
  2. 是否可以使第二个示例以与第一个示例相同的方式工作,以便模板成员函数定义在实例化时考虑成员函数或命名空间作用域函数?
  3. 更新:更改帖子的标题更准确,并对标准中的引号进行了轻微修改。

1 个答案:

答案 0 :(得分:0)

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 引入派生类的函数将被视为派生类的成员。

但这指的是using B::f来自D的{​​{1}},因此不是完美匹配。