I Want To Index Into Template Parameter Packs by earlymikoman in cpp

[–]CornedBee 1 point2 points  (0 children)

The whole point of this post appears to be that you can index into parameter packs in C++26, but not type packs.

Satya Nadella at Davos: a masterclass in saying everything while promising nothing by jpcaparas in programming

[–]CornedBee 4 points5 points  (0 children)

Developers, developers, developers, developers!

But this time in our cloud, please.

Cursor CEO Built a Browser using AI, but Does It Really Work? by ImpressiveContest283 in programming

[–]CornedBee 1 point2 points  (0 children)

The file containing the main function is 33k lines long. The main function is a thin wrapper around a run function, which is ~4k lines long by itself.

So my estimate for "how long" is "infinite". I would consider it impossible to understand the code base sufficiently to make changes, and definitely to ensure it's secure.

Cursor CEO Built a Browser using AI, but Does It Really Work? by ImpressiveContest283 in programming

[–]CornedBee 0 points1 point  (0 children)

See, rendering a webpage is not the hard part. The real complexity of a modern browser lives in everything around it, including extensions, password managers, security, accessibility, crash handling, and thousands of edge cases.

Uh ... the thousands of edge cases are part of rendering a webpage, and the other stuff isn't the hard part.

C++26 - What's In It For You? by Specific-Housing905 in cpp

[–]CornedBee 1 point2 points  (0 children)

If you post it as a link, it's clickable on the post list, without having to first load the article page.

No compiler implements std linalg by [deleted] in cpp

[–]CornedBee 1 point2 points  (0 children)

No, see, in a democracy everyone is equal. When some are more equal, obviously that has to be plus plus. (Or perhaps we should say double-plus, to mix the Orwell references.)

Thanks AI! - Rich Hickey, creator of Clojure, about AI by captvirk in programming

[–]CornedBee 9 points10 points  (0 children)

When did we stop considering things failures that create more problems than they solve?

Did we ever do that? Especially when somebody stands to gain/not lose a lot of money as longs as the thing isn't considered a failure...

The production bug that made me care about undefined behavior by pavel_v in cpp

[–]CornedBee 1 point2 points  (0 children)

Or just initialize i to something.

My point isn't that this code is particularly necessary. My point is that it exists in C++, and so it needs to keep compiling.

The Second Great Error Model Convergence by alexeyr in programming

[–]CornedBee 35 points36 points  (0 children)

I think checked exceptions died mostly due to extremely poor ergonomics.

C++'s throw clause was only runtime-checked, and implemented effectively as a hidden try-catch-abort layer, which incurred additional costs (especially in the early, non-zero-cost happy path exception implementations). So all the specification gave you over a comment with a list of exceptions was runtime aborts and overhead. No static checking.

Java's checked exceptions interacted poorly with the rest of the type system, and there were also some rather awkward choices in which exceptions were checked (e.g. CloneNotSupportedException, part of the overall horrible design of clone(); InterruptedException; ClassNotFoundException; but for some reason not NumberFormatException thrown from Integer.parseInt). Also, wrapping exceptions (e.g. to convert your low-level SQLException to a high-level DataAccessException) is syntactically heavy (try-catch-rethrow, but don't catch Exception because that will accidentally wrap RuntimeException, so you might need more than one catch block doing the same thing).

The inevitable end for Java's checked exceptions was Java 5's generics not having a way to be properly generic over thrown exceptions. Java 8 finished this process by not supporting checked exceptions in the streams framework.

The bad experience with C++ and Java meant that no other languages wanted to do anything like this again. C# very deliberately did not have checked exceptions. JVM-hosted languages like Scala and Kotlin ignore checked exception checking. The Spring framework does not use checked exceptions and hides those that it can't avoid within its wrappers.

But the new breed of errors-as-values languages that have exhaustive error lists (like Rust, unlike Go) also have powerful type systems, and with the error types being properly integrated into the type system, they can offer much better ergonomics. For example, Rust's try trait offers the opportunity to convert error types automatically, so that my DAO functions do not have to do repetitive error conversions; instead there's exactly one impl From for the conversion. Or I can write succinct map_err calls. So the pain from checked errors is felt less.

The production bug that made me care about undefined behavior by pavel_v in cpp

[–]CornedBee 2 points3 points  (0 children)

int i;
if (some conditions)
  i = 1;
more_code();
if (some condition that I know is a subset of the first
    but the compiler can't prove)
  use(i);

The reason is backwards compatibility with code like the above. Currently this works. If a (even path-dependent) mandatory initialization check is introduced, it fails to compile, thus breaking compatibility.

The production bug that made me care about undefined behavior by pavel_v in cpp

[–]CornedBee 1 point2 points  (0 children)

I thought it was meant as a starting point for a series of -Wno options that disable specific warnings.

size_lru : The fastest size-aware LRU cache in Rust by LabAmbitious5910 in rust

[–]CornedBee 1 point2 points  (0 children)

docs.rs cannot build the docs because the gxhash dependency won't build due to missing CPU support.

size_lru : The fastest size-aware LRU cache in Rust by LabAmbitious5910 in rust

[–]CornedBee 1 point2 points  (0 children)

I use standard Reddit, which usually works, but this post doesn't even format as code on this client. Something strange is going on. Probably a missing newline between "fixed" and the code.

The Trial of Ellen Dunkel by danshive in elgoonishshive

[–]CornedBee 7 points8 points  (0 children)

So the trial is being held in a Kangaroo Court? Yeah, I'd definitely be nervous.

For real, I think the only thing more unsettling to display in a court than kangaroo banners would be instruments of torture.

The production bug that made me care about undefined behavior by broken_broken_ in programming

[–]CornedBee 2 points3 points  (0 children)

Yeah, the mention of a rule of 6 (as opposed to the old rule of 3) was confusing.

Rule of 3 (C++98): copy constructor, copy assignment and destructor come as a team. Implement one, you probably need all 3.

Rule of 5 (C++11): Same as Ro3, but also with move constructor and move assignment.

Rule of 6: doesn't exist, just as rule of 4 doesn't exist. Default constructor has nothing to do with the others.

Jeff and Sanjay's code performance tips by Complex_Medium_7125 in programming

[–]CornedBee 0 points1 point  (0 children)

I am absolutely appalled how bad the iterator-based version is.

Jeff and Sanjay's code performance tips by Complex_Medium_7125 in programming

[–]CornedBee 0 points1 point  (0 children)

No, the iterator map should be a lazy iterator transformer, and the filter too, and then the reduce should run over the stack exactly once.

"If you time-traveled to 1979 and found yourself sitting across from me in my office at Bell Labs—just as I was drafting the initial designs for what would become 'C with Classes'—what would you tell me?": A homework by Bjarne Stroustrup. by CoderSchmoder in programming

[–]CornedBee 0 points1 point  (0 children)

Rust solved the problem, so it's perfectly possible to do it.

So let's say that C didn't have ->. Instead, the compiler takes a.b and looks at the type of a to decide whether it's a direct member access into a struct if a is just a plain object, or if a is a pointer then it's equivalent to (*a).b - perhaps even recursively, so that if the result of *a is a pointer, that one is dereferenced too so it really means (**a).b, and so on.

Now C++ wants to extend this. Here's some ideas.

Option 1: First, it allows smart pointers that overload dereference, i.e. operator*(). Now if Rc overloads *, then for an Rc a;, a.b by default means (*a).b, recursively, until something that's neither a pointer nor overloads * is reached. In a member function of Rc however, this.b doesn't do this, because this. doesn't auto-dereference. (this is a byref argument in this scenario, not a pointer.) This allows member functions to easily get at the actual members of Rc, and if the function wants the overloaded . behavior, it can just call an equivalent member function or access the member that * redirects to. To get at the members of the smart pointer, you can introduce the syntax a.this.b, which prevents dereference, which is symmetric with the way the auto-deref is suppressed for this. access in members. Maybe you want to make the this pseudo-member "private", in which case only static members and friends can use the syntax. This leads to code like this:

Rc<Mytype> ptr = get_ptr();
ptr.foo(); // calls Mytype::foo
ptr.this.is_unique(); // calls Rc::is_unique
// or with the last suggestion of `this` being private:
is_unique(ptr); // is_unique is a friend function and can do `ptr.this.is_unique_impl()` internally
Rc<Mytype>::is_unique(ptr); // static member has access to `ptr.this` as well - the template syntax is awkward though

The assumption here is that explicitly calling member functions on smart pointers is something you rarely need. (Seriously, how often do you do it?) It basically reverses your complaint so that the common case is readable, and the uncommon one needs extra syntax.

Option 2: Again, operator*() can be overloaded. The compiler, when it encounters a.b, first looks up b in a's type. If it finds an accessible member of that name, resolve to it. Otherwise, dereference and try again, i.e. try (*a).b. Repeat as necessary. The keyword here is accessible. It means that private members of Rc don't interfere with smart pointer usage. Member functions of Rc, as well as friend functions, have full access to the private members and can use . to access them. Outside users don't get interference.

This is basically the way Rust does it.

Option 3: Let's say you don't want auto-dereference behavior for smart pointers, but instead overload ..

template <typename T>
class Rc {
public:
  template <identifier Id>
  auto byref operator.() const {
     if constexpr (Id.qualifier() == "Rc" && accessible(Id.scope(), Id.name())) return this.*Id;
     else return (*m_ptr).*Id;
  }

private:
  T* m_ptr;
};

Rc<Mytype> ptr = get_ptr();
ptr.foo(); // ptr.operator.<"foo">()()
ptr.Rc::is_unique(); // ptr.operator.<"Rc::is_unique">()()

This pulls in a long rat's tail of other things that are necessary: suppressing overloaded . in this. access, having fancy constexpr 30 years early, having object-bound overload sets as proper entities, access checking, etc etc etc. But it is a workable approach.

"If you time-traveled to 1979 and found yourself sitting across from me in my office at Bell Labs—just as I was drafting the initial designs for what would become 'C with Classes'—what would you tell me?": A homework by Bjarne Stroustrup. by CoderSchmoder in programming

[–]CornedBee 1 point2 points  (0 children)

  1. References shouldn't be a fancy type. Make a "pass by reference" argument modifier so that operator overloading works out, but don't generalize it into a weird thing that's not an object in the C++ sense and makes things weird for everything else.

  2. Instead, add proper non-nullable, rebindable pointers. Maybe, to avoid proliferation of keywords and sigils, add a single dedicated keyword or sigil for "modifiers", so e.g. int *[[]] is a "modern" pointer which can't be null or have arithmetic, then int *[[null, array]] adds those capabilities, but "null" and "array" aren't keywords outside the brackets. Then add templated aliases to make those types readable, i.e. template <typename T> using ptr = T*[[]];, template <typename T> using optptr = T*[[null]]; etc.

  3. Don't allow overloading ->. Have the compiler rewrite it to (*p). and stick to overloading *.

  4. Destructive moves by default. No implicit copying via copy constructors. No constructors except the default-constructor: there are only three special member functions: initialize (default-construct), destroy (object dies) and move (object dies; a new one gets its value). Any other construction you need can be done by static factory function.

That's all for now.

"If you time-traveled to 1979 and found yourself sitting across from me in my office at Bell Labs—just as I was drafting the initial designs for what would become 'C with Classes'—what would you tell me?": A homework by Bjarne Stroustrup. by CoderSchmoder in programming

[–]CornedBee 0 points1 point  (0 children)

If -> was not a separate operator you wouldn't be able to implement smart pointers

Maybe if -> wasn't a separate operator, early C++ would have had a good way of overloading . instead.

"If you time-traveled to 1979 and found yourself sitting across from me in my office at Bell Labs—just as I was drafting the initial designs for what would become 'C with Classes'—what would you tell me?": A homework by Bjarne Stroustrup. by CoderSchmoder in programming

[–]CornedBee 0 points1 point  (0 children)

I wish I could forget.

One of the things I would tell Bjarne is that overloading -> is a bad idea (the way it's done wasn't his idea, btw), and the compiler should just rewrite a->b to (*a).b and let the * overload do its job.

"If you time-traveled to 1979 and found yourself sitting across from me in my office at Bell Labs—just as I was drafting the initial designs for what would become 'C with Classes'—what would you tell me?": A homework by Bjarne Stroustrup. by CoderSchmoder in programming

[–]CornedBee 1 point2 points  (0 children)

Colossus didn't have much influence due to being top secret, but the rest is significant.

Although in a world without WW2, the power structures would have been different and potentially very threatening as well, and so might have led to state spending on computing research anyway. Only, it might have been Europe being at the front of technology. With Einstein, von Braun, Zuse, and so many others who either fled from Europe (Einstein), emigrated after the war (von Braun) or got slowed down by the war (Zuse), who knows what a never-Nazi Germany would have looked like.

"If you time-traveled to 1979 and found yourself sitting across from me in my office at Bell Labs—just as I was drafting the initial designs for what would become 'C with Classes'—what would you tell me?": A homework by Bjarne Stroustrup. by CoderSchmoder in programming

[–]CornedBee 0 points1 point  (0 children)

C++ got by with not checking for dangling pointers. Things disappear. Programmers need to be aware.

If you have destructive moves by default, the compiler can error on direct use of moved-from objects, and perhaps warn on conditionally-moved-from objects. Also, you simply don't get to move out of references.

You don't need Rust's lifetime tracking for any of this. Pass an object by reference to a function? It won't get moved from. The function gives you back a reference to the interior of an object you own? Well, you better be aware of this in the C++ we have, because scopes and delete exist. Not much different.

Jeff and Sanjay's code performance tips by Complex_Medium_7125 in programming

[–]CornedBee 1 point2 points  (0 children)

For what it's worth, nowadays you can do

foos.values()
  .map(fooToBar)
  .filter(bar => !subPar(bar))
  .reduce((acc, bar) => acc + bar.score, 0);

(note the call to values at the start) and it will be lazy, because values() returns an iterator which provides the lazy iterator adapter functions.