【问题标题】:Strange function look up奇怪的函数查找
【发布时间】:2016-03-19 16:17:22
【问题描述】:

当试图掌握 std::ostream_iterator 时,我想出了以下无法编译的代码(在 gcc 5.3 或 clang 3.6 下)。

#include <iostream>
#include <iterator>

namespace temp {
struct Point {
    int x;
};
}

//namespace temp {
//namespace std {
std::ostream& operator<<(std::ostream& s, temp::Point p) {
    return s << p.x;
}
//}

int main(int argc, char** argv) {
    temp::Point p{1};
    std::ostream_iterator{std::cout} = p;
    //std::cout << p;
    std::cout << std::endl;

    return 0;
}

operator&lt;&lt; 在全局范围内时,编译会引发大量模板实例化错误。

但是,std::cout &lt;&lt; p 工作正常。而且,如果在namespace tempnamespace std 中声明operator&lt;&lt;,则代码会按预期编译和运行。

我的问题是为什么全局 operator&lt;&lt; 不起作用?

【问题讨论】:

    标签: c++ c++11 operator-overloading


    【解决方案1】:

    您观察到的行为是 两阶段查找 过程的特性,用于解析从模板定义引用的名称,以及它与参数相关查找 (ADL) 的交互。

    在您的情况下,您使用来自std::ostream_iteratoroperator =。从std::ostream_iterator::operator = 的定义中引用的名称将通过两阶段查找:在第一阶段查找非依赖名称(从operator = 的定义中),而从实例化(您对 operator = 的调用)。

    在内部,std::ostream_iterator::operator = 对给定的 (stream, value) 对使用运算符 &lt;&lt;。由于value 的类型依赖于模板参数,因此对运算符&lt;&lt; 的引用被视为依赖。因此,其决议被推迟到第二阶段。

    确实,查找的第二阶段(从实例化点执行)通常比第一阶段看到更多的名称。而且您显然希望您在全局命名空间中对 operator &lt;&lt; 的定义也会变得可见。

    然而,重要的是要注意关于第二阶段的一个重要细节:在第二阶段,只有 相关的命名空间(由 ADL 引入的命名空间)被“丰富”,在实例化点。但是“常规”命名空间(与 ADL 无关)根本不受第二阶段的影响。在后面的命名空间中,编译器仍然只能看到在第一阶段可见的相同名称,而没有其他任何内容。

    这正是标准中以下段落所说的

    14.6.4 从属名称解析 [temp.dep.res]

    1在解析从属名称时,会考虑以下来源的名称:

    —— 在定义点可见的声明 模板。

    ——来自与类型相关的命名空间的声明 来自实例化上下文的函数参数(14.6.4.1) 并从定义上下文中。

    这解释了您的情况。即使您向 global 命名空间添加了一个额外的 operator &lt;&lt;,但在这种情况下,全局命名空间并不是 ADL 关联的命名空间之一(只有 stdtemp 是)。出于这个原因,第二阶段无法真正看到您额外的&lt;&lt; 定义。

    但是,如果您将定义添加到与 ADL 相关的命名空间之一,第二阶段将立即注意到该添加。这就是为什么如果您在 stdtemp 命名空间中定义您的运算符,您的代码编译得很好。

    【讨论】:

      【解决方案2】:

      这条线有两个问题(除了它没有意义之外):

      std::ostream_iterator{std::cout} = p;
      

      首先,std::ostream_iterator 是类模板,而不是类。所以你可能的意思是:

      std::ostream_iterator<Point>{std::cout} = p;
      

      现在,ostream_iterator::operator= 实际上是如何工作的?它确实依赖于operator&lt;&lt;,但在该类模板的该成员函数定义的上下文中。因此,它将找到的重载是那些在ostream_iteratoroperator= 范围内的重载(你的不是)以及那些可以在参数的相关命名空间中找到的重载(你的不再是)。这就是查找失败的原因。

      如果您只是将operator&lt;&lt; 移动到namespace temp

      namespace temp {
          std::ostream& operator<<(std::ostream& s, Point p) {
              return s << p.x;
         }
      }
      

      或作为非会员朋友:

      namespace temp {
          struct Point {
              int x;
      
              friend std::ostream& operator<<(std::ostream& s, Point p) { ... }
          };
      }
      

      然后依赖于参数的查找成功,这有效:

      std::ostream_iterator<Point>{std::cout} = p;
      

      也就是说,不要编写该代码。使用普通的std::cout &lt;&lt; p


      这是同一现象的另一个示例,可能更容易理解。假设我们有一些函数模板,它只是在其参数上调用另一个函数:

      template <class T>
      void call_f(T val) {
          f(val);
      }
      

      f 将通过从call_f定义点 查找或通过val 上的参数相关查找找到。因此,如果我们稍后执行以下操作:

      namespace N {
          struct X { };
      }
      
      void f(N::X ) { }
      
      int main() {
          f(N::X{});      // ok
          call_f(N::X{}); // error: can't find 'f'
      }
      

      该行错误,因为从 call_f 的定义来看,没有函数 f()(根本)并且在 namespace N 中也没有函数 f。但是如果我们将f 移动到该命名空间中,两个版本都可以正常工作:

      template <class T>
      void call_f(T val) { f(val); }
      
      namespace N {
          struct X { };
          void f(X ) { }
      }
      
      int main() {
          f(N::X{});      // ok
          call_f(N::X{}); // now ok too, ADL finds N::f
      }
      

      【讨论】:

        【解决方案3】:

        我不知道你想用这条线做什么:

        std::ostream_iterator{std::cout} = p;
        

        至于您的实际问题,您可以在全局范围内定义operator&lt;&lt;()

        #include <iostream>
        #include <iterator>
        
        namespace temp {
            struct Point {
                int x;
            };
        }
        
        std::ostream& operator<<(std::ostream& s, temp::Point p) {
            return s << p.x;
        }
        
        int main(int argc, char** argv) {
            temp::Point p{1};
            //std::ostream_iterator{std::cout} = p;
            std::cout << p;
            std::cout << std::endl;
        }
        

        编译并输出1

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-01-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-31
          • 2012-09-01
          相关资源
          最近更新 更多