Yet Another Container: DynamicArray
I've been working on an AllocatorAwareContainer called `dynamic_array` and was wondering if there'd be any interest in including it in Boost. First and foremost, a link to the source code: https://github.com/LeonineKing1199/sleip `dynamic_array` is a heap-allocated array that can't be resized (without entirely re-assigning it). It's useful in cases where you'd normally use `std::vector` but have no need of `push_back`, `emplace_back`, or any of the other size modifiers that the container supports. These kinds of arrays are typically useful in I/O applications which normally only accept a pointer-size pair to write out to. Naturally the question arises, "why use `dynamic_array` in the year 2020 when we have `std::vector` and `boost::allocate_unique`?". `std::vector` imposes a constant storage cost of pointer, size, and capacity. `dynamic_array` only stores a pointer and a size making it slimmer in use-cases where there's no need of a separation between size and capacity. `dynamic_array` also supports constructors `std::vector` doesn't have. For example, `dynamic_array` is constructible from a Range and has a `noinit` constructor. `dynamic_array` even supports array objects which gives it another advantage. `boost::allocate_unique` is a significantly tougher class to beat because it boasts many of the same features: a dynamic allocation done using the supplied Allocator supporting `noinit` construction and array objects with a size (stored in the Deleter of the returned `std::unique_ptr`). And while it is true that `boost::allocate_unique` enables all of this, it still lacks a few key semantics. Namely, `dynamic_array` is copyable. It's also copy assignable and move assignable, including the case where the underlying allocators do not use the same memory resource and will instead perform a piece-wise move of the elements. It's arguable whether or not `std::unique_ptr` should support similar semantics so in the interim, `dynamic_array` is the next best thing. - Chris
I've been working on an AllocatorAwareContainer> called `dynamic_array` and was wondering if there'd> be any interest in including it in Boost. I do not know about the general boost interest.But I find the dynamic array very useful andhave supported a similar utility in my own workin embedded microcontroller systems for over ten years. Naturally the question arises, "why use `dynamic_array`> in the year 2020 when we have `std::vector` and> `boost::allocate_unique`?". For several reasons. The dynamic container isthought to be lightweight in the sense of not pullingin significant amounts of library code. In particularresource constrained systems with closely managedheaps need something like this. In the realm of embeddeddevelopment it can be important to allocate memoryup front at or near the time of static initialization.A kind of dynamic array allows for sizing withinconstructor initializer lists and other things usefulin this endeavor. But how such a thing could or might fit into Boost,I do not know.
Kind regards, Chris. On Thursday, January 23, 2020, 5:04:51 AM GMT+1, Christian Mazakas via Boost <boost@lists.boost.org> wrote: I've been working on an AllocatorAwareContainer called `dynamic_array` and was wondering if there'd be any interest in including it in Boost. First and foremost, a link to the source code: https://github.com/LeonineKing1199/sleip `dynamic_array` is a heap-allocated array that can't be resized (without entirely re-assigning it). It's useful in cases where you'd normally use `std::vector` but have no need of `push_back`, `emplace_back`, or any of the other size modifiers that the container supports. These kinds of arrays are typically useful in I/O applications which normally only accept a pointer-size pair to write out to. Naturally the question arises, "why use `dynamic_array` in the year 2020 when we have `std::vector` and `boost::allocate_unique`?". `std::vector` imposes a constant storage cost of pointer, size, and capacity. `dynamic_array` only stores a pointer and a size making it slimmer in use-cases where there's no need of a separation between size and capacity. `dynamic_array` also supports constructors `std::vector` doesn't have. For example, `dynamic_array` is constructible from a Range and has a `noinit` constructor. `dynamic_array` even supports array objects which gives it another advantage. `boost::allocate_unique` is a significantly tougher class to beat because it boasts many of the same features: a dynamic allocation done using the supplied Allocator supporting `noinit` construction and array objects with a size (stored in the Deleter of the returned `std::unique_ptr`). And while it is true that `boost::allocate_unique` enables all of this, it still lacks a few key semantics. Namely, `dynamic_array` is copyable. It's also copy assignable and move assignable, including the case where the underlying allocators do not use the same memory resource and will instead perform a piece-wise move of the elements. It's arguable whether or not `std::unique_ptr` should support similar semantics so in the interim, `dynamic_array` is the next best thing. - Chris _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Am 23.01.20 um 06:39 schrieb Christopher Kormanyos via Boost:
But how such a thing could or might fit into Boost,I do not know.
Isn't Boost.Container exactly for such "experiments"? There we had the first move-aware vectors, pointer-containers and the like. A dynamic-array sounds like a reasonable addition.
On Thu, 23 Jan 2020 at 05:04, Christian Mazakas via Boost < boost@lists.boost.org> wrote:
I've been working on an AllocatorAwareContainer called `dynamic_array` and was wondering if there'd be any interest in including it in Boost.
I've seen recent talk on the need for a fixed_capacity_vector in the cpplang slack. This looks at first glance like it might fit the bill. In the beast library we have also been discussing the provision of storage types for dynamic I/O buffers which do not un-necessarily initialise data. This kind of thing might be ideal. So yes, I'd say there is certainly interest in the concept, since we're talking about exactly this. Whether it's a candidate for boost is probably for others to decide. IMHO it seems to me that boost.container would be a good home for it.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
On Thu, 23 Jan 2020 at 04:05, Richard Hodges via Boost < boost@lists.boost.org> wrote:
On Thu, 23 Jan 2020 at 05:04, Christian Mazakas via Boost < boost@lists.boost.org> wrote:
I've been working on an AllocatorAwareContainer called `dynamic_array` and was wondering if there'd be any interest in including it in Boost.
I've seen recent talk on the need for a fixed_capacity_vector in the cpplang slack. This looks at first glance like it might fit the bill.
AFAIK, https://github.com/gnzlbg/static_vector is already on it's way for the std [P0843r3 ](https://gnzlbg.github.io/static_vector/), it does not seem a good idea to do a rewrite, without reference to the std [of `std::static_vector`, Vinnie :-), bikeshedding in the paper] being developed and to do a rewrite at all. As is, it does not work on Windows, due to calls to gcc/Clang built-in's, but that would be easy to fix. degski
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Richard Hodges hodges.r@gmail.com office: +442032898513 home: +376841522 mobile: +376380212
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- @realdegski https://brave.com/google-gdpr-workaround/ "We value your privacy, click here!" Sod off! - degski "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding "Growth for the sake of growth is the ideology of the cancer cell" - Edward P. Abbey
On Thu, 23 Jan 2020 at 07:33, degski <degski@gmail.com> wrote:
On Thu, 23 Jan 2020 at 04:05, Richard Hodges via Boost < boost@lists.boost.org> wrote:
On Thu, 23 Jan 2020 at 05:04, Christian Mazakas via Boost < boost@lists.boost.org> wrote:
I've been working on an AllocatorAwareContainer called `dynamic_array` and was wondering if there'd be any interest in including it in Boost.
I've seen recent talk on the need for a fixed_capacity_vector in the cpplang slack. This looks at first glance like it might fit the bill.
AFAIK, https://github.com/gnzlbg/static_vector is already on it's way for the std [P0843r3 ](https://gnzlbg.github.io/static_vector/), it does not seem a good idea to do a rewrite, without reference to the std [of `std::static_vector`, Vinnie :-), bikeshedding in the paper] being developed and to do a rewrite at all. As is, it does not work on Windows, due to calls to gcc/Clang built-in's, but that would be easy to fix.
Oh, yes, forgot one of the most attractive feature, it is fully `constexpr`, i.e. it forms a basis to create any compile time flat [associative] container. degski -- @realdegski https://brave.com/google-gdpr-workaround/ "We value your privacy, click here!" Sod off! - degski "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding "Growth for the sake of growth is the ideology of the cancer cell" - Edward P. Abbey
W dniu 23.01.2020 o 14:36, degski via Boost pisze:
On Thu, 23 Jan 2020 at 07:33, degski <degski@gmail.com> wrote:
On Thu, 23 Jan 2020 at 04:05, Richard Hodges via Boost < boost@lists.boost.org> wrote:
On Thu, 23 Jan 2020 at 05:04, Christian Mazakas via Boost < boost@lists.boost.org> wrote:
I've been working on an AllocatorAwareContainer called `dynamic_array` and was wondering if there'd be any interest in including it in Boost.
I've seen recent talk on the need for a fixed_capacity_vector in the cpplang slack. This looks at first glance like it might fit the bill.
AFAIK, https://github.com/gnzlbg/static_vector is already on it's way for the std [P0843r3 ](https://gnzlbg.github.io/static_vector/), it does not seem a good idea to do a rewrite, without reference to the std [of `std::static_vector`, Vinnie :-), bikeshedding in the paper] being developed and to do a rewrite at all. As is, it does not work on Windows, due to calls to gcc/Clang built-in's, but that would be easy to fix.
Oh, yes, forgot one of the most attractive feature, it is fully `constexpr`, i.e. it forms a basis to create any compile time flat [associative] container.
degski
AFAIU static_vector is a different container. static_vector has compile-time max size, has semantics of std::vector, contains the buffer of elements and therefore has O(N) move which copies elements if they does not support move semantics. dynamic_array has run-time size, has semantics of std::array, contains pointer to dynamically allocated on the heap buffer of elements and therefore has O(1) move. Adam
dynamic_array has run-time size, has semantics of std::array, contains pointer to dynamically allocated on the heap buffer of elements and therefore has O(1) move.
Yup! A static_vector would be an entirely different beast. That would be a container with size modifiers which dynamic_array explicitly avoids.
Wrong. The D in the unique_ptr<T, D> returned by allocate_unique() only stores the Allocator. The size for allocate_unique<T[]>() is stored in D::pointer. i.e. D::pointer is a pointer-like type (detail::alloc_ptr) that stores A::pointer and size.
I apologize and thank you for the correction!
Which means for arrays of sizes known at compile-time,: * dynamic_array<T, A> x(a, N); will store that extra size * auto x = allocate_unique<T[N]>(a); will not
Entirely accurate. This is due to the interface that dynamic_array attempted to emulate which was std::vector and std::array. I'm not sure of a clean way of folding in this kind of storage optimization or what the general interface would look like. People naturally type out std::vector<int> so dynamic_array attempts to look similarly. We could maybe try something like adding a non-type template parameter representing the Extent where -1 is "dynamic" and the size must be stored and all positive values have the optimization applied. I'd love to explore this problem space and see if we can get some solutions in this regard.
In addition to that, boost::allocate_unique() gives you almost everything else dynamic_array gives you: * Allocator aware * Supports construction from a value * Supports default-initialization (noinit) * Supports arrays of arrays
Sounds to me like dynamic_array learned from the best and took a few notes!
And while it is true that `boost::allocate_unique` enables all of this, it still lacks a few key semantics. Namely, `dynamic_array` is copyable. It's also copy assignable and move assignable,
Yes, this is the only difference. The question is if anyone needs this. i.e. When they reach for something lighter than vector<T, A> do they also want that type to be deep copyable/movable.
They would indeed! This is because dynamic_array aims to be a Container. In these kinds of scenarios, if a user is reaching for a Container over a raw array it's usually because they want the semantics of swap, reverse iterators, copyability, etc. In many ways, dynamic_array is a natural extension of the classes already provided to users today.
But if they just want to 'copy or move elements from the result of allocate_unique() that's also not difficult.
Copying is straight-forward but is oftentimes annoying and cumbersome, especially when compared to something as simple as x = y;. Moving is trickier because of allocators not comparing equal. In essence, if one uses allocate_unique with unequal allocators, they cannot simply move the one pointer into the other one if they also wish to use the original allocator. This is because non-equal Allocators can't deallocate allocations made by the other. This is actually a common case when using std::pmr::polymorphic_allocator where it's very likely that two allocator instances will not compare equal. dynamic_array handles Allocators for users whereas allocate_unique does not, nor should it aim to. That's what all the constexpr if is for in the implementation of the class. - Chris
Entirely accurate. This is due to the interface that dynamic_array attempted to emulate which was std::vector and std::array. I'm not sure of a clean way of folding in this kind of storage optimization or what the general interface would look like.
People naturally type out std::vector<int> so dynamic_array attempts to look similarly. We could maybe try something like adding a non-type template parameter representing the Extent where -1 is "dynamic" and the size must be stored and all positive values have the optimization applied.
Currently there is: - Heap storage, resizable: vector - Stack storage, resizable: static_vector - Heap storage, fixed size at runtime: dynamic_array - Stack storage, fixed size at compiletime: array - Heap storage, fixed size at compiletime: unique_ptr<array> Why complicate a new type with this "have the size a template param" stuff when there already is a solution? Usually you want this at an interface: Give me a "buffer" of data which I can change but am not allowed to resize, but as a value type (otherwise there is span) The problem with the template param: You'll have to fix the size at interface design which may not be what you want. And there already is a type for that. Bonus points: template<typename T, size_t N> using heap_static_array = std::unique_ptr<std::array<T, N>>; Yeah I know the interface won't be exactly the same. But I don't agree with "have the optimization applied". It is more than an optimization, its more like a separate type...
On Fri, 24 Jan 2020 at 02:45, Alexander Grund via Boost < boost@lists.boost.org> wrote:
Bonus points: template<typename T, size_t N> using heap_static_array = std::unique_ptr<std::array<T, N>>;
Extra bonus points: template<typename T, size_t N> using heap_static_vector = std::unique_ptr<std::static_vector<T, N>>; degski Note to self: std::static_vector<T, N> could be the base of a fully dynamic stack-allocated deque. -- @realdegski https://brave.com/google-gdpr-workaround/ "We value your privacy, click here!" Sod off! - degski "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding "Growth for the sake of growth is the ideology of the cancer cell" - Edward P. Abbey
On Friday, January 24, 2020, Alexander Grund wrote:
Currently there is:
- Heap storage, resizable: vector - Stack storage, resizable: static_vector - Heap storage, fixed size at runtime: dynamic_array - Stack storage, fixed size at compiletime: array - Heap storage, fixed size at compiletime: unique_ptr<array>
If you're fine with unique_ptr and most people are, you also have:
Heap storage, fixed size at runtime: allocate_unique<T[]>(alloc, size, ...) Gives users everything they need for buffers. (They don't need a Container that is copyable). And for for: Heap storage, fixed size at compiletime: allocate_unique<T[N]>(alloc, ...) Saves bytes on storing size. It's better than unique_ptr<array<T, N>> because it supports arrays-of-arrays: allocate_unique<T[N][M][O]>(alloc, ...) allocate_unique<T[][M][O]>(alloc, size, ...) This facility shipped with Boost 1.72. Glen
On 25/01/2020 01:21, Glen Fernandes wrote:
Heap storage, fixed size at runtime: allocate_unique<T[]>(alloc, size, ...)
Gives users everything they need for buffers. (They don't need a Container that is copyable).
Not quite. These keep the storage pointer and the storage size separately, so you still need a wrapper type (such as the Asio buffer types), or to keep passing them around separately (which is error-prone) or to make assumptions instead of passing it around (which is very error-prone). C++20 introduces std::span, which is a good way of passing around a pointer and size together -- but it's non-owning, so still isn't a solution for the above.
Mere moments ago, quoth I:
On 25/01/2020 01:21, Glen Fernandes wrote:
Heap storage, fixed size at runtime: allocate_unique<T[]>(alloc, size, ...)
Gives users everything they need for buffers. (They don't need a Container that is copyable).
Not quite. These keep the storage pointer and the storage size separately, so you still need a wrapper type (such as the Asio buffer types), or to keep passing them around separately (which is error-prone) or to make assumptions instead of passing it around (which is very error-prone).
C++20 introduces std::span, which is a good way of passing around a pointer and size together -- but it's non-owning, so still isn't a solution for the above.
To clarify: the Asio buffer types are also non-owning. The need for an owning wrapper type is presumably what: On 24/01/2020 21:44, Alexander Grund wrote:
- Heap storage, fixed size at runtime: dynamic_array
was referring to (a type that contains a unique_ptr<T[]> and its size, or equivalent thereof), thus can act as an owning container. (And you can then extract a span or Asio buffer from this, to then pass around without transferring ownership, or move the original container to transfer ownership.) Though I'm not familiar with any existing standard/Boost implementation of this. It probably is a thing that ought to exist. (But that's a terrible name for a container that cannot change size.)
On Tue, 28 Jan 2020 at 22:11, Gavin Lambert via Boost <boost@lists.boost.org> wrote:
C++20 introduces std::span, which is a good way of passing around a pointer and size together -- but it's non-owning, so still isn't a solution for the above.
The pointer in a std::span does not necessarily point at the beginning of the object being owned (that's kind of the point). degski -- @realdegski https://brave.com/google-gdpr-workaround/ "We value your privacy, click here!" Sod off! - degski "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding "Growth for the sake of growth is the ideology of the cancer cell" - Edward P. Abbey
On Tue, Jan 28, 2020 at 10:51 PM Gavin Lambert via Boost <boost@lists.boost.org> wrote:
On 25/01/2020 01:21, Glen Fernandes wrote:
Heap storage, fixed size at runtime: allocate_unique<T[]>(alloc, size, ...)
Gives users everything they need for buffers. (They don't need a Container that is copyable).
Not quite. These keep the storage pointer and the storage size separately,
Yes quite. i.e. What you believe is not true boost::allocate_unique. i.e. For auto p = boost::allocate_unique<T[]>(a, n); assert(p.get().size() == n); This is because the unique_ptr stores a pointer adaptor around the Allocator::pointer which also stores the size. So the pointer and size are stored together in the unique_ptr result. To get the plain Allocator::pointer out of it you can use p.get().ptr() Or, for example, p.release().ptr() for someone who wants to manage it themselves. Glen
On 29/01/2020 17:56, Glen Fernandes wrote:
On Tue, Jan 28, 2020 at 10:51 PM Gavin Lambert wrote:
On 25/01/2020 01:21, Glen Fernandes wrote:
Heap storage, fixed size at runtime: allocate_unique<T[]>(alloc, size, ...)
Gives users everything they need for buffers. (They don't need a Container that is copyable).
Not quite. These keep the storage pointer and the storage size separately,
Yes quite. i.e. What you believe is not true boost::allocate_unique.
i.e. For auto p = boost::allocate_unique<T[]>(a, n); assert(p.get().size() == n);
This is because the unique_ptr stores a pointer adaptor around the Allocator::pointer which also stores the size.
So the pointer and size are stored together in the unique_ptr result.
To get the plain Allocator::pointer out of it you can use p.get().ptr()
Or, for example, p.release().ptr() for someone who wants to manage it themselves.
https://www.boost.org/doc/libs/1_72_0/libs/smart_ptr/doc/html/smart_ptr.html... claims that it returns a std::unique_ptr<T[], D>. This seems incompatible with what you're saying above. (There is also no mention of ptr() or size() there.) If you're returning some mysterious other type to inject size information, isn't this going to become subject to slicing?
On Wed, Jan 29, 2020 at 12:27 AM Gavin Lambert via Boost <boost@lists.boost.org> wrote:
https://www.boost.org/doc/libs/1_72_0/libs/smart_ptr/doc/html/smart_ptr.html...
claims that it returns a std::unique_ptr<T[], D>.
Correct. A std::unique_ptr<T[], D> stores a D::pointer. In our case, D is alloc_deleter<T[], A> and its ::pointer is an alloc_ptr<T[], Allocator>. This alloc_ptr<T[], Allocator> stores an A::pointer and a size_t.
This seems incompatible with what you're saying above.
It isn't.
(There is also no mention of ptr() or size() there.)
It specifies that the ::pointer is an 'unspecified' pointer-like type. I forgot to document that on this unspecified pointer type one can use .ptr() to get the Allocator::pointer and .size() to get the size. Glen
I wrote:
On Wed, Jan 29, 2020 at 12:27 AM Gavin Lambert via Boost wrote:
https://www.boost.org/doc/libs/1_72_0/libs/smart_ptr/doc/html/smart_ptr.html...
claims that it returns a std::unique_ptr<T[], D>.
Correct.
A std::unique_ptr<T[], D> stores a D::pointer.
In our case, D is alloc_deleter<T[], A> and its ::pointer is an alloc_ptr<T[], Allocator>.
This alloc_ptr<T[], Allocator> stores an A::pointer and a size_t.
This seems incompatible with what you're saying above.
It isn't.
(There is also no mention of ptr() or size() there.)
It specifies that the ::pointer is an 'unspecified' pointer-like type. I forgot to document that on this unspecified pointer type one can use .ptr() to get the Allocator::pointer and .size() to get the size.
Basically, a unique_ptr result from an allocate_unique<T[]>(a, n) _has_ to store the n somewhere in the unique_ptr because it needs to know how many objects to destroy and what size storage to deallocate. I also provide access for you to get that n. (I just forgot to put it in the documentation). Glen
On 29/01/2020 18:39, Glen Fernandes wrote:
A std::unique_ptr<T[], D> stores a D::pointer.
In our case, D is alloc_deleter<T[], A> and its ::pointer is an alloc_ptr<T[], Allocator>.
This alloc_ptr<T[], Allocator> stores an A::pointer and a size_t.
This seems incompatible with what you're saying above.
It isn't.
(There is also no mention of ptr() or size() there.)
It specifies that the ::pointer is an 'unspecified' pointer-like type. I forgot to document that on this unspecified pointer type one can use .ptr() to get the Allocator::pointer and .size() to get the size.
Basically, a unique_ptr result from an allocate_unique<T[]>(a, n) _has_ to store the n somewhere in the unique_ptr because it needs to know how many objects to destroy and what size storage to deallocate.
I also provide access for you to get that n. (I just forgot to put it in the documentation).
Does it also implicitly decay to a T*? (I did have a look at https://github.com/boostorg/smart_ptr/blob/master/include/boost/smart_ptr/al... but I don't see any `operator T*`, which is what I'd expect.) If not, that seems like it'd make it hard to pass to something expecting a raw pointer (eg. as method argument, or to construct a std::span). Or unless you know about the extra undocumented .ptr() indirection requirement.
On Wed, Jan 29, 2020 at 12:55 AM Gavin Lambert via Boost <boost@lists.boost.org> wrote:
On 29/01/2020 18:39, Glen Fernandes wrote:
Basically, a unique_ptr result from an allocate_unique<T[]>(a, n) _has_ to store the n somewhere in the unique_ptr because it needs to know how many objects to destroy and what size storage to deallocate.
I also provide access for you to get that n. (I just forgot to put it in the documentation).
Does it also implicitly decay to a T*?
Why would it decay to a T*? It's a pointer-like type, for which operator* and operator-> give you the right thing. It doesn't decay to T* nor does it decay to A::pointer (which may not even be T*, i.e. it could be a fancy pointer).
(I did have a look at> https://github.com/boostorg/smart_ptr/blob/master/include/boost/smart_ptr/al... but I don't see any `operator T*`, which is what I'd expect.)
You shouldn't expect that. For any unique_ptr<T, D> you can only expect that .get() gives you a D::pointer. For default_delete<T> this might be T*. But it always depends on the Deleter.
If not, that seems like it'd make it hard to pass to something expecting a raw pointer (eg. as method argument, or to construct a std::span).
For any given generic unique_ptr<T, D> p; the way to get a T* raw pointer is not just p.get(). D::pointer can always be a fancy pointer (i.e. a pointer-like type that is not a raw pointer). The correct way to get a raw pointer from any potentially-fancy-pointer x is: to_address(x) This can be boost::to_address (C++03 or higher), or std::to_address (C++20 or higher) Glen
Christian wrote:
I'm not sure of a clean way of folding in this kind of storage optimization or what the general interface would look like.
dynamic_array<T, A> could be dynamic_value<T[], A> Then you can distinguish between: dynamic_value<T[], A> // runtime size, stores size dynamic_value<T[N], A> // no need to store size Glen
dynamic_array<T, A> could be dynamic_value<T[], A>
Then you can distinguish between: dynamic_value<T[], A> // runtime size, stores size dynamic_value<T[N], A> // no need to store size
This actually makes sense, IFF the multi-dimensional case is possible. So if T[] and T[N] are valid, then T[N][M] should also be. This increases the scope greatly but has an incredible difficult questions to answer: What would operator[] return? I don't have a good answer... For names I'd certainly agree with vector and array for container types which are resizeable or non-resizeable respectively. While I see the reasoning in "value", as a "regular" type is implied as opposed to a reference type, I think the name should stay array so it can be recognized as a container.
Currently there is:
- Heap storage, resizable: vector - Stack storage, resizable: static_vector - Heap storage, fixed size at runtime: dynamic_array - Stack storage, fixed size at compiletime: array - Heap storage, fixed size at compiletime: unique_ptr<array>
Why complicate a new type with this "have the size a template param" stuff when there already is a solution?
Fair enough! That sounds acceptable to me.
Yeah I know the interface won't be exactly the same. But I don't agree with "have the optimization applied". It is more than an optimization, its more like a separate type...
I tried working through a small implementation and this coincides with my findings perfectly. Definitely not worth it then.
Heap storage, fixed size at runtime: allocate_unique<T[]>(alloc, size, ...)
Gives users everything they need for buffers. (They don't need a Container that is copyable).
Different classes for different usages. If a user needs just a pointer and a size, allocate_unique is perfect. dynamic_array is a Container. It's a higher-level of abstraction.
dynamic_array<T, A> could be dynamic_value<T[], A>
I think this is a strong design from an API perspective but I don't know if most users would be stoked on having to type the [] portion. It make sense for allocate_unique because you also use it for allocating single objects in addition to a contiguous sequence of them. allocate_unique is lower-level and more generalized whereas dynamic_array is higher-level and less general. I think the storage overhead is a mark against the dynamic_array but there's no cure for the disease that isn't just overall worse than living with it.
This actually makes sense, IFF the multi-dimensional case is possible. So if T[] and T[N] are valid, then T[N][M] should also be. This increases the scope greatly but has an incredible difficult questions to answer: What would operator[] return? I don't have a good answer...
You get T[N][M][...] support for free by using boost::first_scalar. If you check out some of the tests for dynamic_array you can see multi-dimensional arrays being tested. It's pretty trivial implementation-wise if you adhere to the idioms set forth by Core. In the case of dynamic_array, a reference to the value_type is returned. In the case of an array type, you basically wind up with: T (&value)[N][M][...]. References to array objects are never pretty in C++, unfortunately. The biggest problem with an approach like this is that you're now inter-mixing C-style arrays with a fully realized container. Maybe it'd be better if somehow we returned something like a mdspan. I'd be very interested in taking the container in a direction like that but I'm not sure how it'd pan out or if it'd be a strong violation of a user's expectations.
On Sat, 25 Jan 2020 at 18:27, Christian Mazakas via Boost < boost@lists.boost.org> wrote:
Different classes for different usages. If a user needs just a pointer and a size, allocate_unique is perfect.
... and std::span, also perfect, [ https://en.cppreference.com/w/cpp/container/span] does the rest: https://github.com/tcbrindle/span . degski -- @realdegski https://brave.com/google-gdpr-workaround/ "We value your privacy, click here!" Sod off! - degski "Anyone who believes that exponential growth can go on forever in a finite world is either a madman or an economist" - Kenneth E. Boulding "Growth for the sake of growth is the ideology of the cancer cell" - Edward P. Abbey
On Wed, Jan 22, 2020 at 11:04 PM Christian Mazakas via Boost <boost@lists.boost.org> wrote:
`boost::allocate_unique` is a significantly tougher class to beat because it boasts many of the same features: a dynamic allocation done using the supplied Allocator supporting `noinit` construction and
Correct so far.
array objects with a size (stored in the Deleter of the returned `std::unique_ptr`).
Wrong. The D in the unique_ptr<T, D> returned by allocate_unique() only stores the Allocator. The size for allocate_unique<T[]>() is stored in D::pointer. i.e. D::pointer is a pointer-like type (detail::alloc_ptr) that stores A::pointer and size. But for T[N] there's no need to store the size. D::pointer is a type that just stores A::pointer. Same for non-array T, there's no size stored. D::pointer is a type that just stores Only A::pointer Which means for arrays of sizes known at compile-time,: * dynamic_array<T, A> x(a, N); will store that extra size * auto x = allocate_unique<T[N]>(a); will not In addition to that, boost::allocate_unique() gives you almost everything else dynamic_array gives you: * Allocator aware * Supports construction from a value * Supports default-initialization (noinit) * Supports arrays of arrays
And while it is true that `boost::allocate_unique` enables all of this, it still lacks a few key semantics. Namely, `dynamic_array` is copyable. It's also copy assignable and move assignable,
Yes, this is the only difference. The question is if anyone needs this. i.e. When they reach for something lighter than vector<T, A> do they also want that type to be deep copyable/movable. If they do, such an adaptor over unique_ptr<T, D> could be useful sense. But if they just want to 'copy or move elements from the result of allocate_unique() that's also not difficult. Glen
participants (8)
-
Adam Wulkiewicz
-
Alexander Grund
-
Christian Mazakas
-
Christopher Kormanyos
-
degski
-
Gavin Lambert
-
Glen Fernandes
-
Richard Hodges