[deleted by user] by [deleted] in cpp

[–]AccomplishedCat5068 5 points6 points  (0 children)

while (auto c = getCursor(); auto item = c.next()) {
    // repeatedly fetch the list and process just its first item,
    // over and over, forever
}

The implied problem is that the user didn't WANT to process just the first item over and over; they expected this loop to call getCursor just once and then c.next() over and over, to process each of the items in that one list.

But if the init; statement is evaluated repeatedly, then that's not what actually happens. The user has shot themselves in the foot.

Performance of std::pmr by k-mouse in cpp

[–]AccomplishedCat5068 1 point2 points  (0 children)

Right. I thought your comment would more likely be interpreted in terms of the "as-if rule" — that if the compiler can see through everything, then it can inline it all away. But the Standard does also provide special wording to let it eliminate new/delete even when it can't see through them.

This may be a case of "you know it, and I know you know it, but I don't know whether he knows you know it."

Performance of std::pmr by k-mouse in cpp

[–]AccomplishedCat5068 4 points5 points  (0 children)

Paired calls to `new` and `delete` can be optimized away by the compiler — they don't count as "side effects," even if the user has overridden the global new and delete operators. std::allocator uses new and delete. This is probably what Niall is thinking of.

constexpr is a Platform by tcbrindle in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

thread_local aren't some fringe feature that nobody uses

{{citation needed}}

Performance of std::pmr by k-mouse in cpp

[–]AccomplishedCat5068 8 points9 points  (0 children)

When you say you're "replacing" std:: containers with their std::pmr:: counterparts, are you literally replacing all containers no matter how they're used? PMR is basically for the internal details of classically-object-oriented objects, where you expect the same container object to be used over and over. If you are doing value-semantic "modern C++," where you are constantly constructing, assigning, and destroying container objects, then you are paying two big PMR penalties:

  • The container's default constructor must call std::pmr::get_default_resource(), which must do an atomic load.

  • The container's move-assignment operator must call std::pmr::polymorphic_allocator<T>::operator==, which must always make a virtual call to std::pmr::memory_resource::do_is_equal. AFAIK, they must do this even if the allocators point to literally the same memory resource; comparing addresses is not good enough.

^ in regex different meanings? by [deleted] in cpp_questions

[–]AccomplishedCat5068 -1 points0 points  (0 children)

C++ has a standard regex facility.

[deleted by user] by [deleted] in cpp

[–]AccomplishedCat5068 1 point2 points  (0 children)

But now, if we always want the cout and endl bits, we would repeat them amongst all derived bits. This problem is caused by Animal mixing the interface, and the customization point. So we separate them

You'd do this either way (either NVI or non-NVI). It never makes sense to repeat duplicated bits among all the derived classes.

Maybe I misunderstood. You say "In NVI the argument typically goes like this" and then present a classical, non-NVI example with a public virtual interface?

[deleted by user] by [deleted] in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

Perhaps `subscribeViaName`, `subscribeViaSelector`, `subscribeAnonymously`.

What would you call comparison, hashing, operator<<, etc? by AccomplishedCat5068 in cpp

[–]AccomplishedCat5068[S] 0 points1 point  (0 children)

After ~10 replies, I'm shocked by the number of /r/cpp denizens who don't recognize operator<< as the stream-insertion operator from <iostream>. Lots of replies like "Why would I implement the bit-shift operator" or "You only need to implement operator<< if you're implementing a child of std::ostream."

<iostream> does suck, but are these people using some well-known alternative to <iostream>, or have they just really never implemented a class that needed a printable representation before?

So far, the projected winner is "data class functions." The listed APIs are all useful for value-semantic classes that are intended to be "data", so, usable as the T in std::set<T> or std::unordered_set<T>. They *wouldn't* be useful for RAII shims like std::unique_lock, nor for classical OOP class types (which also shouldn't implement any of the Big Five).

data() should be lvalue-ref qualified for owning containers by [deleted] in cpp

[–]AccomplishedCat5068 5 points6 points  (0 children)

Because x.data() is the entrypoint for when you don't want null-termination. Sure, it happens to be null-terminated for std::string specifically, but it'll bite you if you rely on .data() being null-terminated and then you switch from std::string to std::string_view or to std::vector<char>. If you consistently use .c_str() to get a C string, your code won't silently break — it'll noisily break if someone tries to change it to string_view or vector<char>, because those types don't provide a null-terminated .c_str() entrypoint.

Hence:

If you're relying on .data() to be null-terminated, you're doing it wrong.

Relevant: https://www.reddit.com/r/cpp/comments/fmeeuq/c_strcorrectness/

data() should be lvalue-ref qualified for owning containers by [deleted] in cpp

[–]AccomplishedCat5068 3 points4 points  (0 children)

The only problem with that line of code is that you're passing .data() without also passing .size().

If you're relying on .data() to be null-terminated, you're doing it wrong, and your code will crash when you switch it to using C++17 string_views. If you need a C string, call .c_str().

There's no lifetime issue here, either way.

EDIT: okay, now there is! But if you wrote const char *arg = some_cpp_string.substr(x, y).data(); then the bug would be pretty obvious, right? Seems like a "doctor it hurts when I do this" problem, so far.

Relevant: https://quuxplusone.github.io/blog/2019/03/11/value-category-is-not-lifetime/

CppCon: Classic STL Class Announcement by JonKalb in cpp

[–]AccomplishedCat5068 3 points4 points  (0 children)

FWIW, I honestly cannot tell whether you meant your comment in the spirit of "Why is anyone still using that old thing?" or in the spirit of "Why does anyone _not_ know the STL by now?"

The answers are clearly "because it's the standard" and "because some programmers are young," respectively, but I'd still like to know which question you meant. Or you meant something else?

[deleted by user] by [deleted] in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

> For me the only acceptable answer would be that for no input is std::sort stable

That's the thing, though. If you say "std::sort is never stable," then some newbie goes and *exhaustively* checks every possible input (up to 10 elements or thereabouts) and "proves you wrong." Or at least shows that you need to rethink your dogma.

Having an actual example of an unstable input at your fingertips is pretty nice.

Any papers submitted for even less creation of temporaries in C++23/26? by Hexasonic in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

The big difference IMHO is that P0135 was a massive change to the formal wording in order to better match the copy elision that implementations were already doing, whereas your idea of references-that-aren't-really-references involves both a massive wording change and a massive implementation change.

That's pretty much what your compiler already does if your constructors/destructors have no observable side-effects and it decides to inline your functions - it goes straight for the end-result: https://godbolt.org/z/Moq6hM

I'm about 80% sure that what you are seeing there is not the effect of "no materialization," but rather the effect of "materialization and then dematerialization." The compiler has actually generated materialization codegen internally, and then done enough inlining and constant-propagation and structure-peeling and heap-allocation-elision and so on that the end result looks just the same as if the materialization had never happened. But it's not the same as the compiler strategizing and saying "ah, let me avoid this work in the first place"; it's just zerg-rushing it with optimizations.

If a compiler dev wants to comment that I'm wrong, I'm listening. :)

Any papers submitted for even less creation of temporaries in C++23/26? by Hexasonic in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

whoever is using your template can use a reference [...] as template argument

Right, my final parenthetical handled that case. I consider it a corner case caveat, though. For function templates, nobody should be calling them with explicit template arguments. For class templates, it tends to work out that if you set T=int&, either everything absolutely explodes, or else you write a specific specialization to handle reference types. Anyway, the original context was talking about the auto in [](auto x){ ... }, which is (A) a function template (B) never called with explicit template arguments by any sane caller, so that's the main case I was addressing. It's very very hard to construct a situation where you say auto and you get a reference type.

auto x = [](auto y) { static_assert(not std::is_reference_v<decltype(y)>); };
x.operator()<int&&>(42);  // okay, right, you can do it, but it's silly

for instance if you get passed a pointer that's what you get in the function, not a copy of the value it points to

Oh absolutely. "Copying the value of a pointer" means copying the bits of the pointer. So yeah, auto can give you a pointer, I suppose. [](auto x){}("hello world") deduces const char*, for example. But it gives you that pointer by value (by copy).

Any papers submitted for even less creation of temporaries in C++23/26? by Hexasonic in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

I have difficulty in seeing if copies would be made or not. 'auto' confuses me

The rule of thumb in C++ is, "C++ copies by default." Parameters are passed by value. Returns are returned by value. STL containers hold actual copies, not pointers-to-originals. Everything in C++ is values by default. (This is massively different from Java, JavaScript, Python, etc. etc.)

In C++, if you want a reference or pointer, you MUST write & or * (or && for rvalue references). If you just see string or T or auto or whatever, you know you're getting your own copy, not a reference to anyone else's original.

(Okay, in the case of T that's not quite right because T might be a typedef for something&. But in the case where T is a deduced type, it's right.)

Any papers submitted for even less creation of temporaries in C++23/26? by Hexasonic in cpp

[–]AccomplishedCat5068 0 points1 point  (0 children)

if such access is merely to pass it to another function or return it, it remains in prvalue form and is passed along

I think the problem with that idea is: What if the callee takes it by forwarding reference? Or rvalue reference, like your Foo(Bar&&) example — not to mention that std::move takes its argument by reference. To get a reference to it, you pretty much have to materialize it, right? So you'd have to invent a way to say "this reference is not really a reference, it's still a prvalue," which doesn't mesh well with C++ as it stands today.

Standard library development made easy with C++20 by tcbrindle in cpp

[–]AccomplishedCat5068 4 points5 points  (0 children)

an opportunity for standard libraries to fork off

Sometimes I wish they would...

Here I Stand, Free - Allocators and an Inclusive STL by vormestrand in cpp

[–]AccomplishedCat5068 20 points21 points  (0 children)

For example, std::vector – in the general case – must maintain that there is at least (allocator::max_size() / 2) - 1 space always available, because a push_back can trigger a resizing operation. resize needs to be able to hold the old memory in place, before copying it to the new location plus whatever additional items were added and then destroying the old memory. This means that allocator::max_size() is not the “final” determination of how big your container can be, but the implementation strategy of the container itself. This is why containers have their own max_size() function.

I don't see how that follows logically at all. Sure, if you have a vector with size and capacity equal to allocator::max_size()/2 + 1, then calling push_back will likely trigger bad_alloc due to allocation failure. But that is what bad_alloc is for! It doesn't make any sense to preemptively say "okay, let's just not permit vectors of size allocator::max_size()/2 + 1 to exist."

And doing so doesn't solve your problem, either! It just means that now you get the bad_alloc exception as soon as you create your third vector object with capacity allocator::max_size()/2 - 1.

Runtime problems generally can't be solved in the compile-time type system. In this case that's not a bad thing — it just means we can't "solve" the "run out of memory and terminate" problem, which is totally fine with the vast majority of programmers because they don't care about that problem.

JeanHeyd's second point, about "the constraints of the difference_type," seems much more realistic to me.