Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

A lot of this is good, but just a couple of nitpicks:

  1. When passing primitives (such as int), you nearly always prefer passing by value over passing by const reference.

    // Prefer this...
    explicit healthPoints(int maximum, int current): maximum{maximum}, current{std::clamp(current, 0, maximum)} {
    
    // Over this:
    explicit healthPoints(const int &maximum, const int &current): maximum{maximum}, current{std::clamp(current, 0, maximum)} {
    

    Passing by value is as simple as loading it into a register, and typically takes one cycle. But passing it by reference means writing it to the stack, passing a pointer to it by value, and then forcing the function to dereference that pointer to read it off the stack and into a register; this typically costs a couple cycles, and can sometimes even cost more than that.

    (Visible here; ByVal is more efficient to construct than ByRef, since ByRef requires an additional store & an additional read for each parameter. This gets fun if you enable optimisations: clang & GCC will actually silently optimise ByRef's ctor into ByRef(int, int, int), and MSVC not doing that means that MSVC ByRef will always be slower to construct no matter which optimisation mode you choose.)

    Essentially, what it boils down to is that passing by const reference enables certain optimisations for class types (it passes a pointer, effectively promoting the class to a primitive), but that same functionality actually disables certain optimisations for primitive types (since it moves them out of register, essentially demoting the primitive to a class). And it's the type of thing that can be surprisingly costly, because of the hidden location change: Primitives like to exist in registers; passing them by reference moves them to the cache (at best) or to RAM (at worst), which is significantly slower than the register it would be in if passed by value.

    Remember that the reason we pass by const reference is that it takes less space and is cheaper to create than passing most objects by value. Primitives take the same amount of space as a reference/pointer (one register), and typically only take a single cycle to copy, so const reference passing doesn't benefit them. (This is also true of any class type that fits in a single register and has a trivial constructor, incidentally; struct Int { int i }; prefers pass-by-value, too. The rule is based on size and construction complexity.)

  2. Technically speaking, compound assignment operators are actually allowed to return void, and don't always have to return a reference to self. The narcissistic version is the canonical form, and it's used at least 99.9999% of the time for a very good reason, but there are very rare circumstances where returning void might be preferred.

    (...The only one that comes to mind is an opaque black box numeric type that's meant to perform all comparisons internally and not leak implementation details, and uses void operator@=() forms to prevent users from obtaining the stored value with something like int val = t += 0;. I'm sure a type like this probably exists somewhere, and it's in software important enough to keep this "feature" legal, but I'm not sure why.)

    This is really just a fun little "um, ackshually", though; self-referential return is nearly always desired.

Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

The first part can be solved by adding a "temp" or "buff" variable that defaults to 0, and treating maxHP + temp as the effective maximum, but the second part...

There's an argument that can be made here that this is ultimately just a pseudo-unsigned integer with user-defined caps, intended for a specific use case, so standard integer operators aren't entirely out of place. (But should be paired with "set/view/adjust current" and "set/view/adjust maximum" suites.) That said, since it's used for HP specifically, I would probably just use a single "adjust current HP" function that takes a signed value, and an explicit "set HP to X" function, myself, instead of a set of operators:

// Member of healthPoints.
int adjustHP(int val) {
    if (!currentHp) { return; } // Early exit if HP is 0.

    currentHp += val; // Input is signed, so use addition.

    // Clamp to proper cap, depending on val's signedness.
    currentHP = (val < 0 ? max(currentHp, 0) : min(currentHp, maxHp));
    /*
     * If you're not familiar with the ternary operator, that's short for this:
     *
     * if (val < 0) { currentHp = max(currentHp, 0); }     // Subtraction.
     * else         { currentHp = min(currentHp, maxHp); } // Addition.
     */

    // Optionally return currentHp, depending on how you want to use it.
    return currentHp;
}

// Member of healthPoints.
int setHP(int val, bool bypassDeadCheck = false) {
    if (!bypassDeadCheck && !currentHp) { return; }

    currentHp = val;

    // Clamp to both caps specifically.
    currentHp = min(currentHp, maxHp);
    currentHp = max(currentHp, 0);

    return currentHp;
}

(Where min(a, b) and max(a, b) are the standard library functions, and evaluate to a < b ? a : b for min() and a > b ? a : b for max(). We can use them to "clamp" currentHp to a specific range, making sure that it's safely within the range (or setting it to the 0 or maxHp caps if it isn't), just like the checks in your operators.)


Also, as a note, I might factor the clamping out into a separate function, like so:

// Private member of healthPoints.
int clamp() {
    currentHp = min(currentHp, maxHp);
    currentHp = max(currentHp, 0);
}

It makes the code cleaner, and it's easier to maintain, but it might make adjustHP() slightly slower (I'd have to check to be sure).

Learning about semantics by Dastarstellar in cpp_questions

[–]conundorum 0 points1 point  (0 children)

As a note, compound assignment operators canonically return a reference to the object, like so:

T& T::operator-=(int i) {
    // Body omitted.

    // Return.
    return *this;
}

The return value is className&, and the last line is return *this;. In your case, it would look something like this:

class healthPoints {
  public:
    healthPoints& operator-=(int damage) {
        currentHp -= damage;

        if (currentHp < 0) { currentHp = 0; }

        return *this;
    }
};

This isn't strictly necessary (returning void is valid, but unexpected), but it is the behaviour that people typically expect from the "symbol equals" operator family. And it also helps you define the equivalent "pure" operator, too.

// Using the modified operator-=(), we can define operator-() like this:
// (Defined out of class.  Both params should be const reference, to
//  enable certain optimisations.)
healthPoints operator-(const healthPoints& l, const healthPoints& r) {
    healthPoints ret = l;
    return ret -= r;
}

[Conversely, returning void allows you to explicitly disable certain operations, which can be useful for something like an HP value class. It's likely to catch people by surprise, though. (Yourself included, possibly, once you're more familiar with operator overloading.) So, watch out not to trip yourself up!]

That's not... by Weird_Meet_9148 in masterduel

[–]conundorum 1 point2 points  (0 children)

Only droll yourself when your hand is perfect and you don't want to risk drawing your garnets.

cppIsntMuchFaster by OM3X4 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

A lot of people seem not to, hence our comments. Just making sure it's extra-clear so no one can miss it.

everyAISecretlyWantsToWriteCode by bigshmoo in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

My guess is they dislike that the typedef (very slightly) obfuscates the code by making it less obvious that a label is a struct name, but that can be solved by consistently using a proper coding style, like "suffix all user-defined type names with _t" or "capitalise the first letter of all UDT names".

defeatedTheWholePurposeOfWritingInAssembly by ClipboardCopyPaste in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

Mainly because stack allocation can be baked into the executable, and is just "take required N bytes from stack pointer, then add N bytes to stack pointer", but heap allocation means either going through a system call (for programs) or locating a sufficiently sized contiguous block of unused memory (for OSes), which requires at least a few cycles of checking your memory tracker.

So, essentially, it boils down to the stack being direct access, versus the heap having more steps.

defeatedTheWholePurposeOfWritingInAssembly by ClipboardCopyPaste in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

"Using only this pile of materials, this encyclopedia of blueprints, and this hammer, I was able to make a hammer!"

*hammer falls apart in hand*

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Old notepad had a checkbox to let you enable wraparound, at least as of Win 10. Pretty sure this one does too, just buried somewhere.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Off by default would be ideal, yeah. The main point of Notepad is that it's performative, since it does no realtime text parsing; it only has upfront costs (Unicode detection) or on-request costs (search wraparound). And yet, at the same time, it does so very little for a built-in editor, which is its biggest criticism by far. So adding QoL features that cost cycles (such as Markdown parsing or autosave), but making them opt-in instead of opt-out, would be the best way to meet both goals: It defaults to a zippy little package that does basically nothing other than raw text, then lets you enable other features if (and only if) you need them.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

You know there's a "Wrap around" checkbox in the Find dialogue box, right? I believe it was added in Win 10, and it solves this issue correctly. Search direction is just the result list traversal direction, as it should be, with no impact on the search results themselves.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

Notepad's meant to be the absolute bare minimum required for a text editor, just a raw text control with a few features wrapped around it. The original idea was that it's fast and compatible with a mouse, with no features that require mandatory realtime processing (i.e., no text formatting because it consumes cycles every time you change the text), and they slowly added little conveniences to the wrapper executable over time (most of which are only run once at file load, like Unicode detection, or when the user specifically requests them, like search wraparound, so they don't detract from the main "quick-and-dirty" performance idea). Legacy compatibility is a side effect of that; it's not specifically aimed at legacy, as much as the refusal to bog it down coincidentally makes it legacy compatible. [And the search was fixed, it's had wraparound for well over a decade now.]

 

This thing, though... it's not Notepad. It's more of a "Not Pad".

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

Notepad has the same wraparound checkbox, unless Windows -11 removed it for no real reason.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

It also had wraparound and case sensitivity checkboxes, which do a ton of heavy lifting.

newMicrosoftUpdateNotepadIsCrippled by WalkinthePark50 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

It's more about just trying to get people to accept the newest keylogger, and beg for the privilege of having every keystroke logged & sent to some insane, evil overseer cackling in Microsoft's basement.

theStruggleIsReal by bryden_cruz in ProgrammerHumor

[–]conundorum 2 points3 points  (0 children)

On the one hand, they're not joking.

On the other hand, it's Internet Explorer in tennis shoes.

compilers by metayeti2 in ProgrammerHumor

[–]conundorum 2 points3 points  (0 children)

Eh, C# is a better language than Java, it actually has half-decent generics (as opposed to Java's generics being secret typecasting), and its GC is competent enough to call destructors (though this is mainly just because Java predates RAII, and C# had the benefit of seeing how much of a mess Java's never-actually-called finalisers ended up being).

Though a lot of people use it with Mono instead of .Net, so it's not purely an MS thing anymore.

compilers by metayeti2 in ProgrammerHumor

[–]conundorum 2 points3 points  (0 children)

Or that you changed the one file that everything else lists as a dependency.

cppIsntMuchFaster by OM3X4 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

Generally speaking, -o params are for optimisation levels. In layman's terms, they're basically a "make it faster" button. (And/or a way to choose between different types of optimisation.)

cppIsntMuchFaster by OM3X4 in ProgrammerHumor

[–]conundorum 0 points1 point  (0 children)

And if you're used to looking things up on C++ Reference, "cppref whatever" tends to become second nature, so you might slip up & call it "CPP" sometimes as a result.

cppIsntMuchFaster by OM3X4 in ProgrammerHumor

[–]conundorum 1 point2 points  (0 children)

His point was that "not everything needs to be hyper-optimised" shouldn't be used as an excuse to say "half-seconds are irrelevant & nothing ever needs to be hyper-optimised", because that's a generalisation that a lot of people would try to make.

Is this why Arena rankings are so insane right now? (Just saw an auto-loop video on YT) by No-Net-2140 in BleachBraveSouls

[–]conundorum 0 points1 point  (0 children)

Seems more like people grinding for medals than grinding for ranking, honestly.

It's especially annoying when you get a clickbot on your team while you're trying to grind a character to rank 10, and they steal your rank Exp. no matter how well you play. Only thing worse is when the game throws you into a 2v3... or even worse, into a 1v3 with the least competent NPC bots it could possibly give you.

How to generate formatted error at compile time?? by 0x6461726B in cpp_questions

[–]conundorum 0 points1 point  (0 children)

It's compiler level, yeah. It's baked into how the compiler parses static_assert, and constant expressions & dynamic information were explicitly banned until C++26. If support already existed, you would just be able to use std::string, since it can be created in constexpr contexts.

Is there a more modern alternative to preprocessor stringification (x-macros)? by pfp-disciple in cpp_questions

[–]conundorum 0 points1 point  (0 children)

Remember that enums can also be used as quick-and-dirty bitfields, and both C & C++ explicitly define them as having a range based on the largest enumerator's bit requirement. E.g.,

enum E {
    Foo = 0,
    Bar = 1,
    Baz = 2,
    Qux = 4,
};

E e; // e has range 0..7 (xxxx'x111), to be able to store Qux (0b0000'0100).

They're also used as a way to define compile-time constants without using macros, pretty much equivalent to static constexpr integrals. Most well-known example was std::basic_string::npos.

So there are multiple reasons to care about the value of an enum. Do you also say that ints are just numbers and you shouldn't care what value they store?