4

I have overloaded operator<< to print a built-in array const int (&arr)[N]:

template <size_t N>
std::ostream& operator<<(std::ostream& os, const int (&arr)[N])
{
    os << "{ ";
    bool first{true};
    for (int i : arr)
    {
        os << (first ? "" : ", ") << i;
        first = false;
    }
    return os << " }";
}

I can use it to print a const int (&)[5] array to std::cout:

    int arr[3][5] = {{3, 4, 6, 1, 1}, {6, 3, 4, 5, 1}, {6, 1, 2, 3, 3}};

    for (const auto& subarr : arr) { std::cout << subarr << "\n"; }

// Outputs:
//
//   { 3, 4, 6, 1, 1 }
//   { 6, 3, 4, 5, 1 }
//   { 6, 1, 2, 3, 3 }

However, when I try to print the same array via a std::copy to std::ostream_iterator<const int (&)[5]>, I just get the array address printed out:

std::copy(std::cbegin(arr), std::cend(arr),
        std::ostream_iterator<const int (&)[5]>{std::cout, "\n"});

// Outputs:
//
//   0x7ffec4f84db0
//   0x7ffec4f84dc4
//   0x7ffec4f84dd8

I suppose the array is decaying to a pointer at the ostream_iterator end. If so, is there a way to avoid that?

[Demo] for a working version.


There are many questions in this site regarding array-to-pointer decay, but I haven't seen one that was helping with this case.

Actually, this answer says:

[...] you can also prevent decay in your original version of f if you explicitly specify the template agument T as a reference-to-array type

f<int (&)[27]>(array);

But that doesn't seem to be happening when constructing the ostream_iterator.

rturrado
  • 7,699
  • 6
  • 42
  • 62
  • 1
    There's always a danger of ADL interference when trying to provide the insertion `operator<<` of a built-in type. (There's probably a rule against that.) You can have an `struct Arr5 { int const (&arr)[5]; Arr5(int const (&a)[5]) : arr{a} {} };` helper class that has the `operator<<` behavior you want, and then call it via `ostream_iterator{cout, "\n"}`. – Eljay Feb 07 '22 at 18:25
  • @Eljay Many thanks! Actually, that was also the solution proposed by Barry in the accepted answer. – rturrado Feb 07 '22 at 19:21

1 Answers1

4

This has nothing to do with array-to-pointer decay and everything to do with how name lookup works.

In this version:

for (const auto& subarr : arr) { std::cout << subarr << "\n"; }

your operator<< is a candidate because regular unqualified lookup will find it.

But that's not what ostream_iterator does, its implementation internally will do something like this:

template <typename T>
void whatever(std::ostream& os, T const& arg) {
    os << arg;
}

And that os << arg call is going to not find your operator<< candidate by unqualified lookup (it won't have been declared at the point of definition of the header) and it's not going to find your operator<< by argument-dependent lookup (since it's not in an associated namespace of either argument). Since your function isn't a candidate, instead the one selected is the pointer one - which is why it formats the way it does.

One (bad, don't do this) solution would be to put your operator<< overload inside of namespace std, which would cause argument-dependent lookup to be able to actually find it. But don't do this, since you're not allowed to add things to namespace std (and, generally speaking, shouldn't add your things to other people's namespaces).

So the better solution would be to instead create your own type that formats the way you want it to, since then you can just add an operator<< that properly associates with it - in a way that doesn't involve messing with std.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks! I also thought about that. Then, I tried this other [little example](https://godbolt.org/z/cj96caz7z), doing the same but with a `struct A`. I saw that my `operator<<` for `const A&` was called, and I dismissed this option. What is my reasoning missing then? – rturrado Feb 07 '22 at 18:43
  • @rturrado Was `A` supposed to have an `int[N]` member, as opposed to an `int`? – Barry Feb 07 '22 at 18:49
  • No, my test simply tried to demonstrate that `std::ostream_iterator` could "see" my `operator<<` overload for `const A&` . But I think I've understood the problem from what you've answered (although I fail to understand much of this ADL stuff): `ostream_iterator` will be able to look up and find `operator<<` for `const A&` by searching where `A` is declared; but it won't be able to find `operator<<` for `const int (&arr)[N]` by searching in the `std` namespace. Is it so? – rturrado Feb 07 '22 at 18:54
  • 1
    @rturrado: `A` is not a buit-in, ADL will look for operator in same samespace than `A`(, so global one). – Jarod42 Feb 07 '22 at 18:55
  • @Jarod42 But, for `const int (&arr)[N]`, i.e. for built-in types, it won't look in the global one though, just in `std`. – rturrado Feb 07 '22 at 18:58
  • 1
    @rturrado It won't look in `std` for built-in types -- for built-in types, only built-in operators are considered. `std` is looked into because it's an associated namespace of the *other* parameter - which is a `std::ostream`. – Barry Feb 07 '22 at 19:29
  • @Barry Ah, OK, thanks! – rturrado Feb 07 '22 at 19:50