为什么我不能将此运算符重载放在与结构相同的名称空间中?

Why I cannot put this operator overload in the same namespace as the struct?

我有以下代码:

#include <iostream>
#include <vector>
namespace X {
    std::ostream& operator<<(std::ostream& os,const std::vector<double>& v){
        for (int i=0;i<v.size();i++){os << v[i] << " ";}
        return os;
    }
    namespace Y {
        struct A {std::vector<double> x;};    
        std::ostream& operator<<(std::ostream& os,const A& a){
            os << a.x << std::endl;
            return os;
        }
   }     
}

using namespace X;

int main(int argc, char** argv) {
   std::vector<double> v(10,0);
   std::cout << v << std::endl;
   Y::A a;
   std::cout << a << std::endl;
   return 0;
}

第一个重载有效,但第二个无效。由于某种原因,它找不到第一个。我收到错误:

no match for 'operator<<' (operand types are 'std::ostream 
{aka std::basic_ostream<char>}' and 'const std::vector<double>')
     os << a.x << std::endl;
        ^

我不明白为什么会出现此错误。例如这样的事情似乎是完全有效的:

namespace A {
    void foo(){}
    namespace B {
        void bar(){foo();}
    }
}

但是,解决上述问题的唯一方法是将第二个重载也放在 X 中。为什么不能将它放在与结构相同的命名空间中(即 X::Y)?

PS:我正在阅读 ADL and I found some related questions (e.g. this and this,但我从阅读这篇文章中了解到,以上内容应该有效。

根据其他答案,我最终推断出 operator<< 的 ADL 受到阻碍,因为它发生在另一个 operator<< 中。

今天的课程:始终根据编写器方法编写 operator<< 重载 :)

修复方法如下:

#include <iostream>
#include <vector>


namespace X 
{
    std::ostream& operator<<(std::ostream& os,const std::vector<double>& v){
        for (int i=0;i<v.size();i++){os << v[i] << " ";}
        return os;
    }
    namespace Y 
    {
        struct A 
        { 
            std::vector<double> x;
            void write(std::ostream&os) const {
                os << x << std::endl;
            }
        };    
        std::ostream& operator<<(std::ostream& os,const A& a)
        {
            a.write(os);
            return os;
        }
    }     
}

using namespace X;

int main(int argc, char** argv) 
{
   std::vector<double> v(10,0);
   std::cout << v << std::endl;
   X::Y::A a;
   std::cout << a << std::endl;
   return 0;
}

就这么简单:为了重载一个函数,重载的版本必须在同一个 nemaspace 中,否则就是一个完全不同的函数。 函数的名称(对于编译器)是从全局命名空间到函数本身的完整路径。

::function_at_global_namespace();
Namespace::function_name();      // Some funtion within a namespace;
Namespace_1::function_name();    // Some other function within another namespace;

所以,

标准 std::ostream& operator<< 存在于 std 命名空间中,您没有重载该运算符,只需在命名空间 X.

中定义另一个运算符

正如@0x499602D2 所指出的,您必须在命名空间 Y 中使用 X::operator<< 才能调用该版本的运算符。

std::ostream& std::operator<<std::ostream& X::operator<<是不同的函数。

在下面的代码中 none foo 版本正在重载。

// What version of foo gets called?  A::foo, or B::foo?
namespace A {
    void foo(){cout << "A::foo" << endl;}
    namespace B {
        void foo(){ cout << "B::foo" << endl;}
        void bar(){foo();}
    }
}

namespace C { void foo(int a) { cout << "C:foo" << endl; } }

在 Argument Depended Lookup(或 Koenig Lookup)中,编译器将在每个 参数.

的父范围内声明的所有符号添加到可见范围

即使 YX 的 "child namespace",它们在 ADL 方面也不相关。您的第一个参数是在 std:: 命名空间中定义的类型,而第二个是局部符号(在与函数本身相同的命名空间中定义)。

请注意,由于上述原因,您很可能会在此行中遇到另一个错误:

std::cout << v << std::endl;

编译器将无法找到为 std::vector<double> 重载的 operator<<(因为它位于 namespace X 中)。

要解决这个问题,您可以使用:

using X::operator<<

namespace Y 内或移动该过载。

如果您想知道,为什么 foobar 示例有效:那是因为 ADL (Argument Dependent Lookup) 是关于函数参数的范围,而不是自己发挥作用。在 foobar 代码中,ADL 未应用。