Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

std::copy with std::ostream_iterator<std::array...>

74 views
Skip to first unread message

Ralf Goertz

unread,
Jan 2, 2020, 4:37:12 AM1/2/20
to
Hi,

I don't get the problem in the following program. If I use

using Coords=Foo;

everything is okay but with

using Coords=std::array<int,2>

I get bombarded with (for me) undecipherable template errors originating
from the std::copy line. Note that without that line it works for both
types, so the operator overload "<<" should not be the problem.

What ist going on

#include <iostream>
#include <iterator>
#include <algorithm>
#include <array>
#include <set>

struct Foo {
int i,j;
bool operator<(const struct Foo &r) const { //needed for set
if (i<r.i) return true;
return j<r.j;
}
};

std::ostream & operator<<(std::ostream &o,const std::array<int,2> &c) {
return o<<'('<<c[0]<<','<<c[1]<<')';
}

std::ostream & operator<<(std::ostream &o,const Foo &c) {
return o<<'('<<c.i<<','<<c.j<<')';
}

using Coords=Foo; //this works
//using Coords=std::array<int,2>; //this doesn't

int main() {
std::set<Coords> ls;
ls.insert(Coords({47,11}));
std::cout<<*ls.begin()<<std::endl;
std::copy(ls.cbegin(),ls.cend(),std::ostream_iterator<Coords>(std::cout," ")); //Compile error with array
std::cout<<"\n";
}

Paavo Helde

unread,
Jan 2, 2020, 5:27:32 AM1/2/20
to
On 2.01.2020 11:37, Ralf Goertz wrote:
> Hi,
>
> I don't get the problem in the following program. If I use
>
> using Coords=Foo;
>
> everything is okay but with
>
> using Coords=std::array<int,2>
>
> I get bombarded with (for me) undecipherable template errors originating
> from the std::copy line. Note that without that line it works for both
> types, so the operator overload "<<" should not be the problem.

Your example starts to compile if you put the relevant operator<< inside
namespace std.

Bo Persson

unread,
Jan 2, 2020, 6:27:10 AM1/2/20
to
On 2020-01-02 at 10:37, Ralf Goertz wrote:
> Hi,
>
> I don't get the problem in the following program. If I use
>
> using Coords=Foo;
>
> everything is okay but with
>
> using Coords=std::array<int,2>
>
> I get bombarded with (for me) undecipherable template errors originating
> from the std::copy line. Note that without that line it works for both
> types, so the operator overload "<<" should not be the problem.

But it is. :-)

Argument dependent lookup will search for an operator in the
namespace(s) of the arguments (thus its name :-).

If you (or a standard algorithm) use an operator<< with two types from
namespace std (for example, std::ostream and std::array), the compiler
will look for an operator<< also in namespace std. There are lots of
them, but none for std::array.

Your operator is in the global namespace, but std::array is not. So that
operator will not be found.

It works for Foo, because the type and the operator are in the same
namespace.

Ralf Goertz

unread,
Jan 2, 2020, 10:10:31 AM1/2/20
to
Am Thu, 2 Jan 2020 12:26:59 +0100
schrieb Bo Persson <b...@bo-persson.se>:

> On 2020-01-02 at 10:37, Ralf Goertz wrote:
> > Hi,
> >
> > I don't get the problem in the following program. If I use
> >
> > using Coords=Foo;
> >
> > everything is okay but with
> >
> > using Coords=std::array<int,2>
> >
> > I get bombarded with (for me) undecipherable template errors
> > originating from the std::copy line. Note that without that line it
> > works for both types, so the operator overload "<<" should not be
> > the problem.
>
> But it is. :-)
>
> Argument dependent lookup will search for an operator in the
> namespace(s) of the arguments (thus its name :-).
>
> If you (or a standard algorithm) use an operator<< with two types
> from namespace std (for example, std::ostream and std::array), the
> compiler will look for an operator<< also in namespace std. There are
> lots of them, but none for std::array.
>
> Your operator is in the global namespace, but std::array is not. So
> that operator will not be found.
>
> It works for Foo, because the type and the operator are in the same
> namespace.

Thanks for the explanation. I figured as much after having read Paavo's
reply. But two questions remain. First, I thought it would be illegal
(or at least frowned upon) for user code to inject anything into the std
namespace. And what is the difference for the line

std::cout<<*ls.begin()<<std::endl;

(ls being of type std::set<std::array<int,2>>) although I have defined
the operator in the global namespace and both operands are in std (I
guess). Shouldn't the rule you quoted also lead to a lookup failure?

James Kuyper

unread,
Jan 2, 2020, 11:13:32 PM1/2/20
to
On 1/2/20 10:10 AM, Ralf Goertz wrote:
...
> reply. But two questions remain. First, I thought it would be illegal
> (or at least frowned upon) for user code to inject anything into the std
> namespace.

"A program may add a template specialization for any standard library
template to namespace std only if the declaration depends on a
user-defined type and the specialization meets the standard library
requirements for the original template and is not explicitly
prohibited." (17.6.4.2.1p1)
"A program may explicitly instantiate a template defined in the standard
library only if the declaration depends on the name of a user-defined
type and the instantiation meets the standard library requirements
for the original template." (17.6.4.2.1p2).

Ralf Goertz

unread,
Jan 3, 2020, 1:50:09 AM1/3/20
to
Am Thu, 2 Jan 2020 23:13:22 -0500
schrieb James Kuyper <james...@alumni.caltech.edu>:
Thanks. Do you also have an answer to the second question:

std::cout<<*ls.begin()<<std::endl;
std::copy(ls.begin(),ls.end(),std::ostream_iterator<Coords>(std::cout," "));

Why doesn't the first of the above lines require the operator << to be
defined in std whereas the second does?

James Kuyper

unread,
Jan 3, 2020, 8:30:48 AM1/3/20
to
On 1/3/20 1:49 AM, Ralf Goertz wrote:
...
> Thanks. Do you also have an answer to the second question:

No - answering that question would require more time than I can afford
to spend right now. I hope someone else answers it first.

Bo Persson

unread,
Jan 3, 2020, 9:09:47 AM1/3/20
to
Yes, but there are more rules (lots of them)...

The compiler also does an Unqualified name lookup

https://en.cppreference.com/w/cpp/language/unqualified_lookup

(about 10 pages of rules) for "<< *ls.begin" and finds the global one
from main(), because the call is done from the global namespace.


The code for std::copy is inside namespace std so the search for an
operator starts there. And it finds lots of operator<< in the namespace,
so it stops there:

"For an unqualified name, that is a name that does not appear to the
right of a scope resolution operator ::, name lookup examines the scopes
as described below, until it finds at least one declaration of any kind,
at which time the lookup stops and no further scopes are examined."


And then the compiler performs an overload resolution (another 30 pages
in the standard) for the names it has found.

The complete process is *way* too complicated to fully describe in a
post here, so the recommendation is to always declare the operators in
the same namespace as (at least one of) the operands, and avoid most of
the complications.



Bo Persson

Ike Naar

unread,
Jan 4, 2020, 4:46:37 PM1/4/20
to
On 2020-01-02, Ralf Goertz <m...@myprovider.invalid> wrote:
> struct Foo {
> int i,j;
> bool operator<(const struct Foo &r) const { //needed for set
> if (i<r.i) return true;
> return j<r.j;

This looks suspect.
Do you want (2,1) to be less than (1,2) ?
if you want to define a lexicographical order on (i,j), the comparison should be

return i<r.i || (i==r.i && j<r.j);

Ralf Goertz

unread,
Jan 5, 2020, 10:06:25 AM1/5/20
to
Am Sat, 4 Jan 2020 21:46:28 -0000 (UTC)
schrieb Ike Naar <i...@sdf.lonestar.org>:
Yeah, I know. I did get it wrong but I only defined the operator in
order to be able to use Foo with the set. I was puzzled by the compile
errors with array. I thought that the fact that array is templated could
be the reason. So I tried the non-templated Foo and the errors were
gone. I would never have guessed that the namespace was the problem.

Jorgen Grahn

unread,
Jan 5, 2020, 1:24:38 PM1/5/20
to
On Sun, 2020-01-05, Ralf Goertz wrote:
> Am Sat, 4 Jan 2020 21:46:28 -0000 (UTC)
> schrieb Ike Naar <i...@sdf.lonestar.org>:
>
>> On 2020-01-02, Ralf Goertz <m...@myprovider.invalid> wrote:
>> > struct Foo {
>> > int i,j;
>> > bool operator<(const struct Foo &r) const { //needed for set
>> > if (i<r.i) return true;
>> > return j<r.j;
>>
>> This looks suspect.
>> Do you want (2,1) to be less than (1,2) ? if you want to define a
>> lexicographical order on (i,j), the comparison should be
>>
>> return i<r.i || (i==r.i && j<r.j);
>
> Yeah, I know.

I don't know why, but I feel safer defining it like this:

bool operator<(const struct Foo &r) const {
if (i==r.i) return j < r.j;
return i < r.i;
}

Another option would be std::make_pair, and comparing the pairs.

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Öö Tiib

unread,
Jan 5, 2020, 1:56:56 PM1/5/20
to
Funny. I checked that most usual in my code is that pattern:

return (l.a != r.a) ? l.a < r.a
: (l.b != r.b) ? l.b < r.b
: (l.c != r.c) ? l.c < r.c
: (l.d != r.d) ? l.d < r.d
: l.e < r.e;

Jorgen Grahn

unread,
Jan 5, 2020, 2:55:29 PM1/5/20
to
"The first pair that's different gets to decide." Makes sense!
0 new messages