3

When trying to get to grips with std::ostream_iterator I have come up with the following code which doesn't compile (under gcc 5.3 or 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;
}

When operator<< is in global scope, compilation throws up a host of template instantiation errors.

However, std::cout << p works fine. And, if operator<< is declare in namespace temp or namespace std, the code compiles and runs as one would expect.

My question is why does a global operator<< not work?

Barry
  • 286,269
  • 29
  • 621
  • 977

3 Answers3

3

There are two problems with this line (outside of the fact that it makes no sense):

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

First, std::ostream_iterator is a class template, not a class. So you probably meant:

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

Now, how does ostream_iterator::operator= actually work? It does rely on operator<<, but in the context of the definition of that member function of that class template. So the overloads that it will find are those in the scope of ostream_iterator's operator= (which yours is not) and those that can be found in the associated namespaces of the arguments (which yours is not again). That's why lookup fails.

If you simply move your operator<< into namespace temp:

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

Or as a non-member friend:

namespace temp {
    struct Point {
        int x;

        friend std::ostream& operator<<(std::ostream& s, Point p) { ... }
    };
}

Then argument-dependent lookup succeeds, and this works:

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

That said, don't write that code. Use the normal std::cout << p.


Here's another example of the same phenomenon that might be easier to understand. Let's say we have some function template that just calls another function on its argument:

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

f will be found by lookup from the point of definition of call_f or via argument-dependent lookup on val. So if we later do something like:

namespace N {
    struct X { };
}

void f(N::X ) { }

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

That line errors because from the point of definition of call_f, there is no function f() (at all) and there is no function f in namespace N either. But if we move f into that namespace, both versions work fine:

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
}
Barry
  • 286,269
  • 29
  • 621
  • 977
3

The behavior that you observe is a peculiarity of Two-Phase Lookup process, which is used when resolving names referred from template definitions, and its interaction with Argument Dependent Lookup (ADL).

In your case you use operator = from std::ostream_iterator. Names referred from the definition of std::ostream_iterator::operator = will be looked up through two-phase lookup: non-dependent names are looked up at the first phase (from the definition of operator =), while dependent names are looked up from the point of instantiation (your call to operator =).

Internally, std::ostream_iterator::operator = uses operator << for the given (stream, value) pair. And since the type of value is dependent on template parameter, this reference to operator << is treated as a dependent one. Thus, its resolution is postponed to the second phase.

It is true that the second phase of lookup (performed from the point of instantiation) generally sees more names than the first phase. And you apparently expected your definition of operator << in global namespace to become visible as well.

However, it is crucial to note one important detail about the second phase: at the second phase only the associated namespaces (namespaces brought in by ADL) are "enriched" with additional names visible at the point of instantiation. But the "regular" namespaces (not related to ADL) are not affected by the second phase at all. In the latter namespaces the compiler is still restricted to seeing the same names that were visible at the first phase and nothing else.

That is exactly what the following passage in the standard says

14.6.4 Dependent name resolution [temp.dep.res]

1 In resolving dependent names, names from the following sources are considered:

— Declarations that are visible at the point of definition of the template.

— Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.

This explains what happens in your case. Even though you added an extra operator << to the global namespace, the global namespace is not one of the ADL associated namespaces in this case (only std and temp are). For this reason, the second phase cannot really see your extra << definition.

But if you add your definition to one of ADL associated namespaces, the second phase will immediately notice that addition. This is why your code compiles fine if you define your operator in std or temp namespaces.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • See https://stackoverflow.com/questions/4447827/why-does-ostream-iterator-not-work-as-expected in case both namespaces are `std` because of a typedef. – Burak Jul 14 '22 at 01:47
0

I have no clue what you are trying to do with this line:

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

As to your actual question, you can define the operator<<() at global scope:

#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;
}

Which compiles and outputs 1.

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • Say we would like to print a set of points with `copy(vec.begin(), vec.end(), ostream_iterator(cout, "\n"))`. – Burak Jul 14 '22 at 01:18