Home > front end >  Can I pass a built-in array to std::ostream_iterator without an array-to-pointer decay?
Can I pass a built-in array to std::ostream_iterator without an array-to-pointer decay?

Time:02-08

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.

CodePudding user response:

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.

  •  Tags:  
  • Related