Live data from Hacker News

Orthodox C++ (2016)

bkaradzic.github.io

221–230 of 238 posts

Re: Orthodox C++ (2016)

#221

Earlier quoted context omitted.

Okay, but if I may offer a suggestion: in the future, I would probably phrase what you said differently. I think it caused you trouble here not just with me but with other readers. Instead of "when the function cannot fulfill its contract", I would talk about "when returning would not fulfill a function's contract." There are actually two reasons for this, not one: - It violates standard terminology. The notion of a…

>How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"? Reporting normal errors to the caller, as opposed to exceptional errors. The distinction between normal errors and exceptional errors is kind of nebulous, but it boils down to normal errors being those that the immediate caller would be interested in, and everything else…

>> How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"?

> Reporting normal errors to the caller, as opposed to exceptional errors. The distinction between normal errors and exceptional errors is kind of nebulous, but it boils down to normal errors being those that the immediate caller would be interested in, and everything else is exceptional.

...

> So to answer your question, exceptions are misused when they communicate relevant errors within the same level of abstraction, such as an open() function throwing a FileNotFoundException.

I think your thoughts feel a little bit jumbled here, possibly because you're oversimplifying things too much. Whether a caller would be interested in an exception doesn't really determine whether it should be an exception or an error code. For example, std::regex_error and std::format_error are very much of interest to the callers of the APIs that throw them (e.g., because the patterns user-provided and the callers expect potential failures) but that doesn't mean they shouldn't be exceptions. Or, for example, the only entity ever really interested in an std::out_of_range exception is the caller (say, catching it around a call like std::accumulate(i, j, [&](const auto& key) { return container.at(key); })).

You're right that those things all matter in a lot of cases, but the line isn't really drawn like that. IMO, the truth is that whether an exception should be raised vs. an error returned is really not so easy to pin down to a single factor. The things you mentioned matter, but so do run-time, compile-time, and development-time costs. The reason FileNotFoundException is better left as an error vs. an exception isn't so much that it's an expected error condition (in fact, some callers might expect it and others not), but because of the performance characteristics callers generally expect. You don't want disproportionate performance impact to those who need to open potentially non-existent files. If C++ exceptions were as cheap to throw as to avoid, then that would tip the balance significantly.

Re: Orthodox C++ (2016)

#222
post #210

Earlier quoted context omitted.

I'm not wrong. It's been removed elsewhere for being LLM generated. This discussion has already been had multiple times. The author used Claude to generate it and instructed it to intentionally insert mistakes. They had comments posted on Reddit that discussed doing this and their account is a slew of AI generated responses. The short responses where it appears they didn't use AI don't reflect the behaviour you'd exp…

You could have elaborated the way you did here to begin with. > shame on you if it's your website, given that you've previously submitted content from there How do you jump to such a conclusion without evidence? Has somebody hurt your feelings on the Internet?

It's a tiny website with low value AI generated content and a very quick look revealed that you've linked to multiple articles from there. It's a fair enough observation that you might be affiliated. I don't think you are, though, hence the 'if'.

Re: Orthodox C++ (2016)

#223

Earlier quoted context omitted.

>How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"? Reporting normal errors to the caller, as opposed to exceptional errors. The distinction between normal errors and exceptional errors is kind of nebulous, but it boils down to normal errors being those that the immediate caller would be interested in, and everything else…

>> How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"? > Reporting normal errors to the caller, as opposed to exceptional errors. The distinction between normal errors and exceptional errors is kind of nebulous, but it boils down to normal errors being those that the immediate caller would be interested in, and everything…

I think your examples don't help your argument.

>std::regex_error and std::format_error are very much of interest to the callers of the APIs that throw them (e.g., because the patterns user-provided and the callers expect potential failures) but that doesn't mean they shouldn't be exceptions

Most commonly, regexes and format string are passed from program data, not user input, so in the majority of cases I don't think it's correct to say that errors are an expected situation. Programmer error is definitely not something that can be handled at the call site, so it makes for a prime candidate to be signaled by an exception.

>Or, for example, the only entity ever really interested in an std::out_of_range exception is the caller (say, catching it around a call like std::accumulate(i, j, [&](const auto& key) { return container.at(key); })).

Why would you write exception handling code around that call instead of ensuring before the call that the indices are valid? If you did ensure that and an exception was still thrown, that's the kind of situation that the caller is not able to resolve, so why would it be interested in the exception? What could it possibly do to recover once it's been proven that the vector was changed concurrently?

>If C++ exceptions were as cheap to throw as to avoid, then that would tip the balance significantly.

No, I don't agree. Sorry for not making my position clearer. What I said previously assumes that you don't have any additional constraints beyond designing the best error handling possible. If you have technical reasons to avoid exceptions (they're too expensive, you're about to cross an FFI boundary, etc.) then that's a reason to stay away from them. I think you should use exceptions up to as much as I said. To use your metaphor, my argument is the floor under the plate of reasons to use exceptions, and the balance can only tip in the other direction.

Re: Orthodox C++ (2016)

#224

Earlier quoted context omitted.

As with the likely/ unlikely branch hint the problem is that programmers are wrong much more often than they expect on both sides . They're too often wrong to think they know the final size - hence Bjarne's caution - but they're also too often wrong that they've got no idea how much capacity they need at all. So hence this API. You're correct that this isn't a huge optimization. But it more than pulls its weight dire…

There are many many situations where I know exactly what is the common path and what is the uncommon path. And when I don't know, it's much harder to optimize! If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation. Maybe chunk lists could be better, overhead is bounded by chunk size and all…

> If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation.

I think this is "Trump math" but assuming we're actually looking at the same over-allocation this isn't new, what you're calling "wasting a factor of 2x" was already what you were paying by using the amortized O(1) growable array type, that's its central trade off, so yeah, the type with that property does still have that property.

> Maybe chunk lists could be better, overhead is bounded by chunk size and all operations are constant-time.

Basically std::deque. Surely no more need be said ?

Re: Orthodox C++ (2016)

#225
post #194

Earlier quoted context omitted.

They're not useable for anything serious, i.e. high throughput, low frequency, massively concurrent work. In other words, most of the things for which you shouldn't better have chosen a different language in the first place. They're also unusable by the way because of ergonomic and software architecture factors, such as bad modularity, terrible compile times, unreadable error messages, unreadable symbol names... Yes…

If you haven't been thinking deeply about memory management and concurrency, you won't be able to understand, no offense meant I do think about that, when needed . My point is that these containers can be 'good enough' in places where it doesn't really matter, not that they're always the go-to thing. E.g. I really don't see any issue using a map as part of a configuration type of object which gets read from args or j…

Yes. When you don't have any specific requirements, any mediocre data structure will do.

Re: Orthodox C++ (2016)

#226

Earlier quoted context omitted.

There are many many situations where I know exactly what is the common path and what is the uncommon path. And when I don't know, it's much harder to optimize! If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation. Maybe chunk lists could be better, overhead is bounded by chunk size and all…

> If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation. I think this is "Trump math" but assuming we're actually looking at the same over-allocation this isn't new, what you're calling "wasting a factor of 2x" was already what you were paying by using the amortized O(1) growable array type,…

> was already what you were paying by using the amortized O(1) growable array type

As I said, I don't think growable array data structure is a good idea.

> Basically std::deque. Surely no more need be said ?

Ok now you're really out of your depth here. Pretty sure you haven't actually used it, it's a somewhat obscure data structure even though the more serious C++ programmers have heard of it, most have probably never used it.

From a web search, std::deque seems to be pretty universally thought of as a weird and very bad data structure. I've had to learn myself because I did actually try to use it myself recently. Beyond being unintuitive to use due to being "abstract" (had 2-3 serious bugs, e.g. unexpected iterator invalidations that happened only under load), apparently std::deque is not even specified as a chunk list. O(1) random access must be guaranteed. So it must be much more complicated than that, I don't know the details though.

And while actual implementations do use chunks in some way (just not in a simple chunk list), apparently "chunks" is not a part of the std::deque spec, and hence there isn't anything standardized about the size of these chunks either.

- On MSVC, chunk size is 16 elements so chunk size of std::deque char is 16 BYTES !!!!! I was thinking they can't be serious, but apprently it is the case

- On GCC, chunk size is 512 byte fixed.

- On Clang, chunk size is 4096 byte fixed.

It _is_ a shithole, surely no more need be said?

Re: Orthodox C++ (2016)

#227

Earlier quoted context omitted.

> If you're counting pennies, Vec::reserve() (inexact) is hardly what you want, because in the worst case you're wasting a factor of 1.5x or 2x of elements due up-front overallocation. I think this is "Trump math" but assuming we're actually looking at the same over-allocation this isn't new, what you're calling "wasting a factor of 2x" was already what you were paying by using the amortized O(1) growable array type,…

> was already what you were paying by using the amortized O(1) growable array type As I said, I don't think growable array data structure is a good idea. > Basically std::deque. Surely no more need be said ? Ok now you're really out of your depth here. Pretty sure you haven't actually used it, it's a somewhat obscure data structure even though the more serious C++ programmers have heard of it, most have probably neve…

> As I said, I don't think growable array data structure is a good idea.

It is still quite hard for me to take this seriously as a belief.

> Ok now you're really out of your depth here.

I don't think so. As you found, the consensus is that this sort of thing is a terrible idea. The O(1) complexity looks good superficially but your actual performance is miserable.

> I don't know the details though.

Raymond Chen has a pretty good explanation of the three implementations of std::deque, like he did for their std::string but Raymond's goal is to help you do forensics, he's not here to tell us which data structures are a good idea. https://devblogs.microsoft.com/oldnewthing/20230810-00/?p=10... might work, or, given Microsoft, it might already be dead by the time you click it.

Re: Orthodox C++ (2016)

#228

Earlier quoted context omitted.

> was already what you were paying by using the amortized O(1) growable array type As I said, I don't think growable array data structure is a good idea. > Basically std::deque. Surely no more need be said ? Ok now you're really out of your depth here. Pretty sure you haven't actually used it, it's a somewhat obscure data structure even though the more serious C++ programmers have heard of it, most have probably neve…

> As I said, I don't think growable array data structure is a good idea. It is still quite hard for me to take this seriously as a belief. > Ok now you're really out of your depth here. I don't think so. As you found, the consensus is that this sort of thing is a terrible idea. The O(1) complexity looks good superficially but your actual performance is miserable. > I don't know the details though. Raymond Chen has a…

> The O(1) complexity looks good superficially

Without knowing more about the details of the spec and of real world implementations [0], I'll boldly claim that the O(1) is the precise problem why this is a weird data structure (I never needed or expected that sort of constraint). It is not a chunk list, like many (including me) seem to have assumed.

I don't even have performance problems measured with this, also given that my std::deque is not currently in production, it is used as a simple streaming queue that only has a couple megabytes/second of bandwidth requirements, and it is not being built with MSVC (so chunks are going to be 512 or 4096 bytes). But it is a latent problem.

[0] Btw. I don't want to know the messy details of this right now, I need simple not complex. I've learned not to add more complexity in a futile attempt to fix things that are fundamentally broken. What use case is this going to be solved by std::deque, name me one such use case and tell me with a straight face that it's not a completely made up case or a super niche case that would likely also be better served by a different approach.

Or take it from someone else if you would, is Chandler Carruth high-profile enough and free enough of conflict of interest to be credible for saying that std::deque is dumb? https://news.ycombinator.com/item?id=22962980

Re: Orthodox C++ (2016)

#229

Earlier quoted context omitted.

> As I said, I don't think growable array data structure is a good idea. It is still quite hard for me to take this seriously as a belief. > Ok now you're really out of your depth here. I don't think so. As you found, the consensus is that this sort of thing is a terrible idea. The O(1) complexity looks good superficially but your actual performance is miserable. > I don't know the details though. Raymond Chen has a…

> The O(1) complexity looks good superficially Without knowing more about the details of the spec and of real world implementations [0], I'll boldly claim that the O(1) is the precise problem why this is a weird data structure (I never needed or expected that sort of constraint). It is not a chunk list, like many (including me) seem to have assumed. I don't even have performance problems measured with this, also give…

> It is not a chunk list, like many (including me) seem to have assumed.

The thing C++ programmers tend to expect (though perhaps not you) is Rust's std::collections::VecDeque - the growable array type again but used as backing for a ring buffer.

This type has amortized O(1) push and pop at both ends, I assume since you didn't like the amortized growth of Vec / std::vector you'll feel the same way but that's what most programmers actually want from such a type - in use as a queue it won't repeatedly cycle allocations so long as the size is constrained, because it's a ring buffer. If you actually know the needed size up front the overhead from this structure, which can grow, versus a structure which can't grow, is a single integer for the capacity.

But as you saw, std::deque is... not that. STL wasn't able to explain what it's for when I asked him, so, if anybody knows it apparently doesn't include the people maintaining the C++ standard library implementations.

> Or take it from someone else if you would

I think the trouble here is that you've misconstrued this entire part of the conversation. I was gesturing at std::deque because it's terrible, and your response, that it's terrible, isn't a rebuttal as I think you're expecting. We agree on that, std::deque is terrible, our disagreement seems to be that you think a slightly different terrible data structure would be a good idea somehow and I do not.

Re: Orthodox C++ (2016)

#230

Earlier quoted context omitted.

> The O(1) complexity looks good superficially Without knowing more about the details of the spec and of real world implementations [0], I'll boldly claim that the O(1) is the precise problem why this is a weird data structure (I never needed or expected that sort of constraint). It is not a chunk list, like many (including me) seem to have assumed. I don't even have performance problems measured with this, also give…

> It is not a chunk list, like many (including me) seem to have assumed. The thing C++ programmers tend to expect (though perhaps not you) is Rust's std::collections::VecDeque - the growable array type again but used as backing for a ring buffer. This type has amortized O(1) push and pop at both ends, I assume since you didn't like the amortized growth of Vec / std::vector you'll feel the same way but that's what mos…

I don't know the size up front because it goes up and down depending on load. I want to be able to buffer jitters/spikes but not hog memory when there is no data in the queue. I was assuming it's natural to want a simple linked list of chunks to represent this, maybe chunks can be partially full here. So chunk -> chunk -> chunk, and each chunk is actually a node with a buffer pointer + size + fill fields, or maybe the data comes directly after the size + fill fields so no buffer pointer needed.

That easily gives O(1) access to enqueue/dequeue, and the queue naturally grows and shrinks (even within configurable bounds) as part of reading and writing. Blocks can be taken and returned from/to some fixed size block pool. This design would be completely satisfactory. It's also very easy to code from scratch, so why should I settle for anything less, for example a growable vector where a reallocation would mean blocking other threads for extended time? But I was told to "not reinvent the wheel" and to "not overengineer it" so I tried to change my mind and make use of an STL container data structure.

> our disagreement seems to be that you think a slightly different terrible data structure would be a good idea

I don't know why you think that I would be thinking that, and I'm confused that you say you were gesturing at how std::deque is terrible, and not sure why I should be the one misconstruing something, when my premise from the beginning was "STL containers are terrible in many ways". And I don't get at all why you responded with "basically std::deque" to my "maybe chunk lists would better". It doesn't make the least bit of sense to me.

But let's settle this here, this is a pointless fight...

Post reply on HN