Page MenuHomePhabricator

[libc++] Remove redundant empty specialization in std::array
AbandonedPublic

Authored by ldionne on May 29 2020, 5:38 AM.

Details

Reviewers
zoecarver
miscco
EricWF
Group Reviewers
Restricted Project
Summary

Instead of having two full specializations for std::array, factor the
common code into an helper class. I was careful not to change the
behavior of std::array, especially ABI wise.

The only purposeful functionality change is that when the debug mode is
enabled, accessing a non-empty std::array out-of-bounds will _LIBCPP_ASSERT,
which seems like an improvement.

Diff Detail

Event Timeline

ldionne created this revision.May 29 2020, 5:38 AM
Herald added a project: Restricted Project. · View Herald TranscriptMay 29 2020, 5:38 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript
ldionne marked an inline comment as done.May 29 2020, 5:40 AM
ldionne added inline comments.
libcxx/include/array
290

Careful -- the diff is a bit confusing since it aligns the old specialization of std::array<T, 0> with the new std::array<T, N> base template. That's why it seems like the old diff was incorrectly implemented as iterator end() _NOEXCEPT {return iterator(data());}, when it really was correctly implemented because that was for the empty-array case.

ldionne updated this revision to Diff 267207.May 29 2020, 6:00 AM

Fix issues in C++14 mode

EricWF requested changes to this revision.May 29 2020, 9:45 AM
EricWF added a subscriber: EricWF.

Why is this code an improvement?

First, it causes substantially fewer misuses of array<T, 0> to be caught.
It admits for all sorts of out-of-bounds memory accesses that weren't possible before.

This revision now requires changes to proceed.May 29 2020, 9:45 AM

Why is this code an improvement?

First, it causes substantially fewer misuses of array<T, 0> to be caught.
It admits for all sorts of out-of-bounds memory accesses that weren't possible before.

How is that true? It's actually quite the opposite -- before, we would *only* catch invalid accesses on std::array<T, 0> -- with this patch, we catch them for any array size. Look at std::array<T, N>::operator[], which now has _LIBCPP_ASSERT when it previously had nothing (only the std::array<T, 0> one had an assert).

Apart from that significant improvement, it also doesn't duplicate the whole definition of std::array just to handle the empty case.

What if we did this in three patches?

  1. Update everything to use data() instead of __elems_.
  2. Use __array_storage in both implementations.
  3. Remove the array<T, 0> specialization.

It also might be good to move data() into __array_storage and hold an instance of __array_storage itself (the element storage would exist in __array_storage). That would make it easier to have many specialized overloads, i.e. one for N = 0 where T is trivial, where N = 0 and T is not trivial, and where N != 0. Then the array implementation only has to worry about implementing itself in terms of data().

libcxx/include/array
128

Do we use a __detail namespace anywhere else? I don't have an issue with it but, it seems a bit out of place.

ldionne marked 2 inline comments as done.May 29 2020, 10:57 AM

What if we did this in three patches?

  1. Update everything to use data() instead of __elems_.
  2. Use __array_storage in both implementations.
  3. Remove the array<T, 0> specialization.

I'm not sure what huge benefit that would yield.

It also might be good to move data() into __array_storage and hold an instance of __array_storage itself (the element storage would exist in __array_storage). That would make it easier to have many specialized overloads, i.e. one for N = 0 where T is trivial, where N = 0 and T is not trivial, and where N != 0. Then the array implementation only has to worry about implementing itself in terms of data().

We can't do that because array has to be an aggregate. It has to contain the T __elems[N] array as its sole member.

libcxx/include/array
128

True, I can remove it.

I'm not sure what huge benefit that would yield.

We don't have to, that's just a suggestion. I think it would be easier to review and if there's a bug in one part of the implementation, we have to revert less code (and it's easier to track down).


I think the direction here will depend on D80821.

We can't do that because array has to be an aggregate. It has to contain the T __elems[N] array as its sole member.

I think we can inherit from __array_storage as a public base. The reason I think it's important to have the data logic factored out into another object is that if we do what I suggest in D80821 we're going to have four overloads which will be more difficult to maintain if they're all in one class. If we don't do what I suggest in D80821, then I'm fine with the implementation in this patch.

ldionne marked an inline comment as done.May 29 2020, 11:32 AM

I'm not sure what huge benefit that would yield.

We don't have to, that's just a suggestion. I think it would be easier to review and if there's a bug in one part of the implementation, we have to revert less code (and it's easier to track down).


I think the direction here will depend on D80821.

We can't do that because array has to be an aggregate. It has to contain the T __elems[N] array as its sole member.

I think we can inherit from __array_storage as a public base. The reason I think it's important to have the data logic factored out into another object is that if we do what I suggest in D80821 we're going to have four overloads which will be more difficult to maintain if they're all in one class. If we don't do what I suggest in D80821, then I'm fine with the implementation in this patch.

Not prior to C++17. Aggregates can't have base classes in C++11 and C++14.

I have to say that this is rather worse than what we had before.

With this change we have

  • A higher implementation complexity. What was previously super simple is now quite complex
  • Slower debug performance trough the __data() indirection

And we gain what? It is not like there will be too much changes to array in the forseeable future that make the unification worthwile.

Note that I am totally in favor to unify span into one implementation. But this is because both the dynamically and statically typed implementation are semantically identical and there is no measurable implementation cost. That cannot be said here as array<T, 0> is completely different than array<T,N != 0>

ldionne abandoned this revision.Jun 2 2020, 8:38 AM

I have to say that this is rather worse than what we had before.

With this change we have

  • A higher implementation complexity. What was previously super simple is now quite complex

How is it quite complex? I'm just de-duplicating all the member functions like size() & friends. IMO it's a lot simpler after the change. I'll abandon this for now and revisit once the situation with array<T, 0> has settled -- this patch can be made simpler then.