I've been reading Nevin Liber's N4416 "Don’t Move: vector Can Have Your Non-Moveable Types Covered"
I have often wished for a member function of std::vector<T> that could give me a vector with capacity exactly n and no more — for example, if I know my vector's maximum capacity up front and I want to be economical with my memory usage; or if I'm using a user-defined allocator that is good at allocating blocks of one exact size and bad at bigger sizes.
Surely it has occurred to everybody on the Committee by now that it would be super convenient if std::vector<T>::reserve were that function; i.e., if we changed its semantics from (B) above to (C)if capacity() < nreserve space for exactly n elementselsedo nothingHowever, in 17 years, this change has not happened. This suggests that there's a really good reason for its not happening. What is that reason? Clearly one reason would be "vendor inertia", but vendors are doing pretty well these days at keeping up with library changes, so maybe that shouldn't be the only blocking reason.
Does anyone on the Committee have some inside information on the reasons for vector::reserve's current (IMHO suboptimal) specification?
Finally, let me open the bikeshed by suggesting that if semantics (C) can't be given to vector::reserve(size_type n), they should at least be given to a new overload vector::reserve(size_type n, decltype(std::reserve_exactly)). Either way, I see no reason for N4416's proposed semantics (A) to ever make it into the language; they just seem strictly less useful and less efficient than semantics (C).
On 4 May 2015 at 22:41, Arthur O'Dwyer <arthur....@gmail.com> wrote:I've been reading Nevin Liber's N4416 "Don’t Move: vector Can Have Your Non-Moveable Types Covered"That paper looks familiar. :-)I have often wished for a member function of std::vector<T> that could give me a vector with capacity exactly n and no more — for example, if I know my vector's maximum capacity up front and I want to be economical with my memory usage; or if I'm using a user-defined allocator that is good at allocating blocks of one exact size and bad at bigger sizes.Even if you have a user-defined allocator with those properties, there really is no way (other than specializing vector on the allocator type) for vector to communicate with the allocator to determine the optimal block size to use. The vector keeps track of capacity independent of the allocator.
Surely it has occurred to everybody on the Committee by now that it would be super convenient if std::vector<T>::reserve were that function; i.e., if we changed its semantics from (B) above to (C)if capacity() < nreserve space for exactly n elementselsedo nothingHowever, in 17 years, this change has not happened. This suggests that there's a really good reason for its not happening. What is that reason? Clearly one reason would be "vendor inertia", but vendors are doing pretty well these days at keeping up with library changes, so maybe that shouldn't be the only blocking reason.The committee works on proposals. I don't know if anyone has proposed this in the past. I can see a few reasons:1. AFAIK implementations just pass the reserve size to the allocator anyway.2. vector<bool> cannot enforce this w/o more bookkeeping.3. It isn't all that useful w/o something like emplace_back_capped() (or something convoluted like a stateful allocator that allows exactly one allocation).
Finally, let me open the bikeshed by suggesting that if semantics (C) can't be given to vector::reserve(size_type n), they should at least be given to a new overload vector::reserve(size_type n, decltype(std::reserve_exactly)). Either way, I see no reason for N4416's proposed semantics (A) to ever make it into the language; they just seem strictly less useful and less efficient than semantics (C).With (C) you cannot store non-moveable types, because the real algorithm is:if capacity() < nreserve space for exactly n elementsmove_if_noexcept elements from old space to new spacedestroy elements in old spacerelease old spaceelsedo nothingVarious ideas are now floating around for setting exact reserve capacity. We'll see if any of them survive after the presentation of this paper later this week.
On 2015–05–05, at 11:41 AM, Arthur O'Dwyer <arthur....@gmail.com> wrote:I've been reading Nevin Liber's N4416 "Don’t Move: vector Can Have Your Non-Moveable Types Covered"He proposes a new member function of std::vector<T> whose name is "subject to L(E)WG bikeshedding" but tentatively proposed as std::vector<T>::reserve_initially(size_type n). The semantics of this function would be (A)if empty()reserve space for exactly n elementselsethrow an exception
I've been reading Nevin Liber's N4416 "Don’t Move: vector Can Have Your Non-Moveable Types Covered"He proposes a new member function of std::vector<T> whose name is "subject to L(E)WG bikeshedding" but tentatively proposed as std::vector<T>::reserve_initially(size_type n). The semantics of this function would be (A)if empty()reserve space for exactly n elementselsethrow an exception
I like the idea, but IMO we really should not be throwing an exception here. Just make it a precondition of the function that the vector must be empty. If someone is catching and actually "handling" the exception, they're just using the exception for control flow and the catch can simply be a branch. The rationale is less than satisfying.
As for the initial suggestion here, to make the new reserve_initially function unnecessary by changing the behaviour of reserve() this is not possible as it can't be instantiated for a non-movable type, which was the reason to introdice the reserve_initially() function in the first place, according to N4416.At the same time, I don't see the need to mandate that reserve_initially() does not allow reservation of additional elements. The programmer obviously knows what the max limit is but it does no harm (except maybe some lost memory) to allocate a bit more.
On 2015–05–07, at 8:51 PM, Nicol Bolas <jmck...@gmail.com> wrote:Besides consistency with the wording on the standard `reserve`, I don't see anything to be gained from allowing implementations to allocate extra elements.
On 2015–05–07, at 8:57 PM, David Krauss <pot...@mac.com> wrote:For std::string, it enables the SSO. Perhaps the generic description should be constrained a bit for std::vector, but that’s all.
P.S. — I would also love to see and/or write a proposal that forbids std::vector<T not equal to bool> to examine its allocator::rebind, just because that keeps biting me every time I try to test anything involving allocators. I keep inheriting from std::allocator and forgetting to override rebind.)http://melpon.org/wandbox/permlink/83azNz80bH1l8B73 (I feel great, my code works fine with Clang)http://melpon.org/wandbox/permlink/ZiPX2zMmqMdyjbkX (aw crap, my code doesn't even get called with GCC)
On Thursday, May 7, 2015 at 4:04:55 PM UTC-4, Arthur O'Dwyer wrote:P.S. — I would also love to see and/or write a proposal that forbids std::vector<T not equal to bool> to examine its allocator::rebind, just because that keeps biting me every time I try to test anything involving allocators. I keep inheriting from std::allocator and forgetting to override rebind.)http://melpon.org/wandbox/permlink/83azNz80bH1l8B73 (I feel great, my code works fine with Clang)http://melpon.org/wandbox/permlink/ZiPX2zMmqMdyjbkX (aw crap, my code doesn't even get called with GCC)
We don't define the allocator model for specific containers; we define how it works, period. How any individual container implementation uses it is up to that implementation. allocator::rebind is part of the allocator interface, and if an implementation chooses to use it, that's perfectly fine.
The correct way to fix this is with a proper allocator concept. Then, you'll get a compiler error if you try to use a non-conforming-but-would-work-on-this-implementation allocator.
On Thu, May 7, 2015 at 6:03 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Thursday, May 7, 2015 at 4:04:55 PM UTC-4, Arthur O'Dwyer wrote:P.S. — I would also love to see and/or write a proposal that forbids std::vector<T not equal to bool> to examine its allocator::rebind, just because that keeps biting me every time I try to test anything involving allocators. I keep inheriting from std::allocator and forgetting to override rebind.)http://melpon.org/wandbox/permlink/83azNz80bH1l8B73 (I feel great, my code works fine with Clang)http://melpon.org/wandbox/permlink/ZiPX2zMmqMdyjbkX (aw crap, my code doesn't even get called with GCC)
We don't define the allocator model for specific containers; we define how it works, period. How any individual container implementation uses it is up to that implementation. allocator::rebind is part of the allocator interface, and if an implementation chooses to use it, that's perfectly fine.I see your rationale, but at the same time I disagree with it. I have a piece of software that deals with vectors of SixteenByteRecords, and for many parts of the codebase I don't need heap-allocation; I can get by with allocating out of a large static array or even a 4K buffer preallocated on the stack. It's easy to preallocate space for a bunch of SixteenByteRecords (guaranteeing alignment and making sure that I can serve them up really quickly simply by advancing a pointer into the buffer), and it's easy to write an allocator that can allocate them. This is not a general-purpose allocator; it's a speed optimization that is specialized to allocate SixteenByteRecords only.The problem is that some vendors' implementations of std::vector<SixteenByteRecord> go out of their way to rebind and even to allocate things that are not SixteenByteRecords, at which point all my performance gains go out the window (on those specific vendors' implementations). I can't blame the vendors, because they're operating within the current spec for std::vector. The only recourse is to abandon std::vector and replace all uses of it with MyVector (which is bit-for-bit identical except for a few lines in reserve). This is the sort of problem that standardization is meant to solve!
(You say "We don't define the allocator model for specific containers", but of course the Standard does define the model; it just happens to leave it a little bit too broad in the case of std::vector. I believe the wording change to forbid vector from calling rebind would be a single additional sentence.)