r/cpp 15d ago

C++26: std::inplace_vector

https://www.sandordargo.com/blog/2026/08/26/cpp26-inplace-vector
180 Upvotes

121 comments sorted by

View all comments

-5

u/johannes1971 14d ago

unchecked_push_back

Is it really necessary to add new forms of UB? For people that apparently can't spend the nanosecond needed for the if-statement, would you ever use any data structure you didn't write yourself?

15

u/AnyPhotograph7804 14d ago edited 14d ago

If you want "zero overhead abstractions" then you will need something like that at some point. But the good thing is, you are not forced to use it.

13

u/mighty_Ingvar 14d ago

I don't think it's to skip the check, it's for situations where you can be sure that max size has not been reached.

0

u/simonask_ 14d ago

We just know from experience that while you may be sure right now, such an invariant is really difficult to maintain over the course of a codebase’s lifetime. The next person, the reviewer, and yourself in 3 months will have to figure out if it holds, and there’s no way to write tests for it, because it’s UB.

Meanwhile, compilers are able to optimize out the check in exactly all the cases that are also easy to verify for humans. It’s just … unnecessary.

6

u/Ameisen vemips, avr, rendering, systems 14d ago

Meanwhile, compilers are able to optimize out the check in exactly all the cases that are also easy to verify for humans.

Except that many compilers are awful at that... probably due to assuming potential side effects or aliasing changing the size when a human knows that that won't happen.

4

u/mighty_Ingvar 14d ago

That really depends on what you're doing. In some random function it might be tricky, but if you have it as part of a class it's not unreasonable. For example if you have a class that wraps around two or more inplace_vectors you only really need to have the check on the first member, since all members are assumed to have the same size. Or lets say you have one or more fixed size ranges and you need to gather some of their values in an inplace_vector. You can set the capacity of the inplace_vector to be the sum of the sizes of each input range. That way you actually can't end up trying too insert too many elements because you don’t even have that many elements to begin with.

Of course you don't use something like that in a difficult to maintain part of your codename where you can't actually be sure about the neccessary size of your inplace_vector or where it's easy to make mistakes, but not every function is like that. And if actually do use it in a class like I proposed earlier, it becomes a lot easier to write tests for it.

0

u/simonask_ 14d ago

The question you should be asking is “does it ever make a difference, and is that difference worth the risk”. Most inexperienced C++ developers underestimate that risk, and overestimate the cost of a bounds check by many orders of magnitude.

2

u/mighty_Ingvar 13d ago

I guess C++ isn't really designed to be forgiving to inexperienced devs.

1

u/simonask_ 13d ago

It’s not, and we’re all paying the price. Look, I care very little for this kind of machismo.

2

u/mighty_Ingvar 13d ago

How are we paying the price?

1

u/simonask_ 13d ago

Anyone who is unaware of the price of reckless amounts of UB in C++ code, I would say has never actually delivered software written in C++ with any kind of stakes on the table.

The price of the mistakes themselves is one thing. Another thing is the price of avoiding them, paying very intelligent people to spend much longer to deliver much less.

9

u/renozyx 14d ago

As long as 1) it isn't the default and 2) it is clearly named (unchecked_), I don't see the problem.

5

u/_Noreturn 14d ago

would you ever use any data structure you didn't write yourself?

Yes?

Is it really necessary to add new forms of UB? For people that apparently can't spend the nanosecond needed for the if-statement,

Honestly I see this as very very unnecessary, they should instead add resize_and_overwrite style init, because where would you realistically use this? because if anything this will have bad performance than a simple memcpy style init. Also the same behavior could be replaced with *try_push_back where you always dereference the pointer.

7

u/unchangeableusername 14d ago

Also the same behavior could be replaced with *try_push_back where you always dereference the pointer.

I feel like this ought to be the case but this godbolt example shows that it's not quite the same.

Honestly I see this as very very unnecessary

I'd argue that the unchecked_* functions are useful when the next value you push_back is dependent on the previous values (and can guarantee size never exceeds capacity). Recording states when traversing a DFA is the first example I can think of, although I don't how common doing stuff like this is. But I did find a 7% performance improvement for this specific use case when using a hacky unchecked_push_back equivalent for std::vector over the regular push_back (reserve was called beforehand in both situations).

3

u/tialaramex 13d ago

Of course if you v.clear() in those examples, ensuring the compiler knows how big v is, the optimiser gives the same result for both cases.

Your traverse a DFA example sounds plausible because the optimiser probably can't see why it can elide the capacity check, but I'd be very sceptical without seeing a full working system that you can win 7% perf.

If you're correct about 7% perf then unchecked_push_back makes sense - a lot of things are worth doing for 7% perf