奇怪的功能查找

时间:2016-03-19 16:17:22

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

当试图掌握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<<处于全局范围内时,编译会引发大量模板实例化错误。

然而,std::cout << p工作正常。而且,如果在operator<<namespace temp中声明了namespace std,则代码会按照预期编译并运行。

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

3 个答案:

答案 0 :(得分:2)

这一行存在两个问题(除此之外没有任何意义):

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

首先,std::ostream_iterator是一个类模板,而不是一个类。所以你可能意味着:

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

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

如果您只是将operator=移至operator<<

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 << 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()中也没有函数f。但是如果我们将namespace N移动到该命名空间中,两个版本都可以正常工作:

f

答案 1 :(得分:2)

您观察到的行为是两阶段查找过程的特性,该过程在解析从模板定义引用的名称及其与参数依赖查找(ADL)的交互时使用。

在您的情况下,您使用operator =中的std::ostream_iterator。从std::ostream_iterator::operator =的定义引用的名称将通过两阶段查找查找:在第一阶段(从operator =的定义)查找非依赖名称,同时查找依赖名称从实例化的角度来看(你打电话给operator =)。

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

确实,第二阶段的查找(从实例化开始执行)通常会看到比第一阶段更多的名称。而且你显然希望你在全局命名空间中定义operator <<也是可见的。

但是,注意第二阶段的一个重要细节至关重要:在第二阶段,只有关联的命名空间(由ADL引入的命名空间)是&#34;丰富的&#34;在实例化时可以看到其他名称。但是&#34;常规&#34;命名空间(与ADL无关)不受第二阶段的影响。在后面的命名空间中,编译器仍然只能看到在第一阶段看到的相同名称而不是其他名称。

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

  

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

     

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

     

-   在定义时可见的声明   模板。

     

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

这解释了您的情况会发生什么。即使您向全局命名空间添加了额外的operator <<,在这种情况下,全局命名空间也不是ADL关联的命名空间之一(仅stdtemp是)。因此,第二阶段无法真正看到您的额外<<定义。

但是如果将定义添加到ADL关联的命名空间之一,则第二阶段将立即注意到该添加。如果您在stdtemp命名空间中定义运算符,那么这就是您的代码编译正常的原因。

答案 2 :(得分:1)

我不清楚你要用这条线做什么:

docker volume rm <volume-name>

关于您的实际问题,您可以在全球范围内定义std::ostream_iterator{std::cout} = p;

operator<<()

编译并输出#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; }