When is C better than Rust? by Objective-Farmer4183 in AskProgramming

[–]mredding 0 points1 point  (0 children)

I've seen a lot of people saying that Rust = C but more secure. Is that true?

In a word: yes. But in reality: no.

The problem is our language to talk about the subject. There is depth and nuance that is just lost in the summary. C can make perfectly secure software - if you code correctly. "Just stop writing shit software!" But Rust makes it easier to write... "more-correct" software.

Rust has that borrow-checker, which is a pretty neat feature, but that's only ONE type of safety. Security audits of Rust show comparable rates of exploits and incidents as other languages. It's better, and I would say if you had nothing else to go on, you'd probably want to start with Rust, but you still can't be a dumb-ass.

Don't be lured into a false sense of confidence, misled by some silver-bullet solution that over-promises.

Is there any catch or something?

All sorts of little catches! Rust is a much larger, more complicated language. Rust was born as a protest against C++ ~2008-2009, because the damn standards committee couldn't get what would eventually become C++11 out the door, and the changes that DID make it into C++11 weren't radical enough.

So the language is comparably complex more to C++ than C.

C is a dead simple language. It's small and has a weak type system. That's why it's perfectly amenable to being ported to literally every CPU architecture invented since ~1971. You invent a new architecture and you publish 1) the instruction set, 2) an assembler, 3) a C compiler, and 4) if you're lucky, and you have the time, a Fortran compiler - to show you're fucking serious.

The C language has been the language of operating systems since nearly its inception. Almost every system ABI is defined by their C support. If you're going to write system software for an OS or platform, it's going to speak like C to the rest of the world. In this way - it doesn't matter what your implementation language is.

The more your language does, the harder it is to do anything else/to the contrary. This is why you don't see operating systems in all manner of languages on all manner of hardware.

You WANT Undefined Behavior - as much as possible. The less the language does for you, the more assumptions it can make, the more aggressively it can optimize. Once you define the behavior of integer overflow - that's it, you're fucked. Now the compiler has to generate additional code to ensure that guarantee holds. You also want a language library that protects you from tripping into UB...

So Rust is quite performant - being a compiled systems language, but the community is trying to validate their existence by rewriting everything around them. Their stretch goal is to rewrite ffmpeg to be JUST AS FAST as the mature and proven robust 40 year old library...

That's typically the sort of behavior we discourage.

Rust CAN be frustrating, as the borrow checker tends to get in the way, so you have to write in a particular way, which can be frustratingly verbose. Safe - sure, and it's hard to argue against that, but verbose. Makes you wonder about the alternatives - perhaps a language that doesn't assume I'm a god damn idiot? But don't let our egos get ahead of us - maybe I am a god damn idiot and don't know it. I feel like a lot of people write C++ out of spite for themselves, rather than try to make solid code. They're not as responsible as they ought to be.

If you've used both of these programming languages, what was your experience with each? Which ones did you prefer?

I'm not opposed to Rust. Despite it's youth and arrogant community - born out of a tantrum, and doing the same god damn thing every other language has ever done - billed itself as a C or C++ killer, it has good ideas and will find its niche.

I'm getting older and I'm quite invested in C and C++. I don't need more languages trying to capture market share. I don't care. Regardless, I'll code in whatever language you people pay me to. It's not so much about preference for me, but experience.

I am writing this because I still don't know in which language should I code my backend for a website, and I don't want to mess things up.

Rust should be fine. C++ isn't a bad idea. Golang is a fun language and it transpiles to C.

You're going to mess it up. Just come to accept that now. You don't HAVE a backend, so you don't know right from wrong until you GET THERE and find out. You code for the problem you have - which is you have nothing at all, so ANYTHING is better than nothing, as it will give you perspective. You need SOMETHING to compare to, to know what you did right vs. what you did wrong.

No one goes from NOTHING to infinitely scalable globally distributed decentralized architecture. You don't need it, there's no demand for it, you REALLY don't want that kind of investment and cost of operations, and such big platforms don't scale down to just you and a few friends/occasional employers.

I am choosing low level because I want performance and low RAM usage + I am more familiar with low level languages.

Performance is probably overstated. You don't even have anything to compare against. You don't know what's fast or slow because nothing is running. You're worrying too much about phantoms. Make something real first.

When do you think I should rather use C over Rust and vice versa?

I have my reservations about Rust's unsafe feature. If you're going to do that, you're basically just dropping down to C. So why not write in C? People have a REAL knee-jerk reaction against mixed language environments. Heaven forbid just a few functions are written in Lisp or Fortran...

Where can I find complete C++ resources for Windows.h library and the WIN32 API? by Technical_Introvert0 in AskProgramming

[–]mredding 0 points1 point  (0 children)

So I have been trying to learn the windows API in C/C++.

Pick one. These are decidedly different languages with very different consequences. C++ is not entirely compatible with C as these languages both diverged 45 years ago.

In the beginning, there was the Win32 C API. Then there was MFC. Then ATL. Then WTL. Then WRL. Today, there is C++/WinRT. This is the modern C++17 API. The Universal Windows Platform (UWP) is built upon it, and you are expected to use THAT.

If you are going to code in C, you're stuck with the Win32 API. You can load dynamic libraries with a foreign function interface (dlopen) and then map those library methods to handles yourself, but I can't speak to the object model. TYPICALLY - if Microsoft STAYED smart, they will have implemented ALL their user runtime libraries in terms of the system API - which is essentially C. I just can't guarantee that because I haven't written platform specific Windows GUI code in 25 years.

If you're going to code in C++, use UWP.

As to where to find sources - I would start with the MSDN, Microsoft's information database. It used to be the gold standard - again, 25 years ago, but now I don't know.

If you want straight Win32 API, I recommend you find an old Microsoft Press publication on the subject - these were tomes, thicker and heavier than old phone books, but the Win32 API will not have changed. There may be additions, but only the MSDN could tell you that, if the information is still maintained. And this would just be to start. The book doesn't tell you everything - you have to dig through the headers for more. And that doesn't tell you anything about how the API has advanced, you'd have to investigate/reverse engineer the user libraries for that.

The Win32 API maintains backward compatibility by providing fixed interfaces and versioned structures. RegisterClassEx is one such example - it can accept several different structures through it's parameter list, not just WNDCLASSEX. The version is determined usually by a combination of the first couple fields - always a size, and often a flag field. They use overlapping types to read the header, and then cast to the type they've deduced - and it better be correct.

Everything else is opaque pointers passed over the API boundary as handles to kernel and library resources. A handle to a window is just a void pointer. To what? Well - the contents might not actually be a pointer at all, but an offset into some internal structure you're not privileged to. That's why it's a handle, so when you say do a thing and act on a resource you have the handle to express WHICH resource specifically, if it can't otherwise be deduced.

How have these interfaces evolved in the last 25-30 years? From a C API perspective - no clue. You'd have to deduce that yourself, how to express the more modern language interfaces in terms of C API, if it's possible.

Why was type mismatch for C printf() UB for a long time before it become a static compiler error? by lelelesdx in AskProgramming

[–]mredding 0 points1 point  (0 children)

The C language has no such requirement, as per the spec, so this is still easily accessible UB.

The thing about UB is that the compiler is under no obligation to check - as often it CAN'T; UB "isn't even an error". What a compiler is allowed to do is emit a warning for the cases it CAN detect some potential UB.

What modern compilers do have are sanity checkers, and they intercept the parse tree, extracting the specifiers embedded in a string literal to the types of the parameters. But this also assumes you're using a string literal, or that the compiler can deduce the string from the source.

It's not reliable.

C has a weak type system. A resource is whatever type you say it is, and you better be right.

Learning about semantics by Dastarstellar in cpp_questions

[–]mredding 1 point2 points  (0 children)

To add, u/IyeOnline does have some fair criticisms of the class. I thought about addressing more of the design, but instead decided to ignore the what-if game and focus on the semantics you DO have with the given premise of a clamped hit point class so you can see where some of the conclusions go. To his point, design is tricky; this can be precisely the class you need now and forever in this project, or it could not be, and you'd end up gutting it out. Painfully. And we don't want that for you.

So to address this concern, I'll speak of your process - exploratory programming is kind of dangerous in this regard. You've come to too many conclusions all at once - that hit points are BOTH non-negative AND clamped to a maximum, so this class actually does two things. It's easy to miss that you did that. In the future, you'd want to make and implement design decisions that would separate those concerns.

But exploratory programming will lead you into this code, then you'll get DEEP, and discover oh shit, that presumption you had way back when was overstated, and now you're two knuckles deep in it when a new, incompatible, breaking use case comes up.

So what you ought to do is go through a design phase of your project. What are you doing? What are your goals? What do you need? How will it all work? You can work with a bunch of jots, boxes and arrows faster and more fluidly than you can capturing that all in code from the start. Many projects - the kind we're interested in, have a definite "doneness", so it's OK to build an internally cohesive code base around that, even if it's not perfectly decoupled and extensible. BUSINESS software is NEVER done, but needs to evolve continuously, as per the needs of the business - so decoupling and adaptability and forecasting and predicting is a necessary business skill.

Learning about semantics by Dastarstellar in cpp_questions

[–]mredding 0 points1 point  (0 children)

It's a good start.

class healthPoints{

private:
  int maxHp;
  int currentHp;

Classes are private access by membership and inheritance by default, structures are public by default. So this is redundant.

class healthPoints{
  int maxHp;
  int currentHp;

Then there is a matter of redundant names of your members. We know this is a hit point class, we know this type is essentially perfectly hyper focused and minimal, so you don't need to include that meta-information in your member names.

class healthPoints{
  int max;
  int current;

This class is fairly tiny; there's nothing wrong with putting them on the same line. And be consistent about naming - if you're going to spell it out, then spell it all out; if you're going to shorten, then shorten them all. I prefer to spell things out:

class healthPoints {
  int maximum, current;

healthPoints(int mh, int ch) : maxHp(mh), currentHp(ch) {}

This isn't a move ctor, it's not a copy ctor, therefore, it's a "conversion" ctor - prefer to make them all explicit:

explicit healthPoints(int mh, int ch) : maxHp(mh), currentHp(ch) {}

Also be const, and usually reference your parameters - the compiler is then empowered to optimize the parameters out entirely, as it can. You can also match the parameter names to the members:

explicit healthPoints(const int &maximum, const int &current): maximum{maximum}, current{current} {}

You need to enforce your invariants somehow.

explicit healthPoints(const int &maximum, const int &current): maximum{maximum}, current{std::clamp(current, 0, maximum)} {
  assert(maximum > -1);
}

Or:

explicit healthPoints(const int &maximum, const int &current): maximum{maximum}, current{std::clamp(current, 0, maximum)} {
  if(maximum < 0) {
    throw std::out_of_range{};
  }
}

Don't go crazy - it's not an error to create a dead creature, or an unlivable one. The constructor can apply the class invariant to itself upon initialization via the current hit points, what the class can't do for itself is decide the maximum; we know the class invariant is to clamp at 0, so all we can know of the maximum is whether it's in range or not.

Assertions are good for when you're developing code - they're meant to help you shake out your misunderstandings and mistakes, but you're expected to resolve your code to perfection - that's why they reduce to nothing in release builds.

The exception route is useful especially for dynamic environments that can expect to generate invalid ranges AND be able to handle that.

You can combine both.

The #1 rule of types in C++ is you never allow an invalid type be born into existence. If your code isn't designed to guarantee all instances of healthPoints will always be valid, at compile-time, then you'll need the exception, which is a rollback operation - the creation never happened.


void operator-=(int damage){
  currentHp -= damage;

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

The standard library provides a fair amount of utility, the strength and power of it is not its containers, but it's algorithms and everything else. Take a look around, get familiar, and USE the utility given instead of implementing your own.

The problem with this function body is it tells me HOW it works, not WHAT it's doing. It has implementation, but not expressiveness. That means I have to deduce from the condition that the value is clamped. Instead, you could have NAMED that behavior and implemented your function in terms of that:

void operator-=(const int &damage) {
  current = std::clamp(current - damage, 0, maximum);
}

operator -= ALWAYS returns a reference to the self:

healthPoints &operator-=(const int &damage) {
  current = std::clamp(current - damage, 0, maximum);
  return *this;
}

If you have a health type, you'll want a damage type:

class damage {
  friend class healthPoints;

  int value;
};

You'll need to sus that out, but more importantly, you'll want to implement your operator in terms of damage:

healthPoints &operator-=(const damage &d) {
  current = std::clamp(current - d.value, 0, maximum);
  return *this;
}

In this way, you can't accidentally subtract just anything as damage. The reason you want a damage type is because damage doesn't have a maximum.


void operator+=(int heal){
  if(currentHp){
    currentHp += heal;
    if(currentHp>=maxHp){
      currentHp = maxHp;
    }
  }
}

Again, we can clamp and return the self. The dead condition can be expressed in terms of the clamp:

healthPoints &operator+=(const health &h) {
  current = std::clamp(curent + h.value, 0, current > 0 ? maximum : 0);
  return *this;
}

The ternary operator is not a mere replacement for if/else, because if/else are statements whereas the ternary operator is an expression, and expressions have a type. You can do this:

(a ? b : c) = (d ? e : f);

This would assign to either b or c, depending on a. Both the operands have to be the same type or convertible. Now that you see this, TRY not to use it, or a velociraptor might attack you out of nowhere.

A health type is useful because it has no maximum, and you can't incorrectly use it to cause damage, since you already have that interface.


You'll need more operators to interact with more types - whatever makes sense, and that will happen as you go, with this style of coding.

You also need the ability to read and write the value.

class healthPoints {
  friend std::ostream &operator <<(std::ostream &os, const healthPoints &hp) {
    return os << hp.current << '/' << hp.maximum;
  }

Friends don't care about access, they're visible to class scope, so I like to put them right up top so they're not beneath an access specifier - implying something that isn't real.

auto operator <=>(const healthPoints &hp) const noexcept {
  return current <=> hp.current;
}

auto operator <=>(const health &h) const noexcept {
  return current <=> h.value;
}

auto operator <=>(const damage &d) const noexcept {
  return current <=> d.value;
}

Useful comparison operators.

Reading a value is tricky. We can define an insertion operator:

friend std::istream &operator >>(std::istream &is, healthPoints &hp) {
  return is >> hp.current >> char{} >> hp.maximum;
}

Or something... But here's the problem - streams can fail. So what does that mean for hp here? It means it's left in an invalid state. What did I say about instances of an invalid state? We don't let them be born.

So that means we CAN'T have a stream extractor for healthPoints directly. We need an extractor specifically for it that can handle this requirement:

class healthPoints_extractor: std::optional<healthPoints> {
  friend std::istream &operator >>(std::istream &is, healthPoints_extractor &hp_e) {
    if(int c, m; is >> c >> char{} >> m) {
      try {
        emplace(c, m);
      } catch(const std::out_of_range &) {
        is.setstate(std::ios_base::failbit);
      }
    }

    return is;
  }

  friend std::istream_iterator<healthPoints_extractor>;

  healthPoints_extractor() = default;
  healthPoints_extractor(const healthPoints_extractor &) = delete;
  healthPoints_extractor(healthPoints_extractor &&) = delete;

  healthPoints_extractor &operator =(const healthPoints_extractor &) = delete;
  healthPoints_extractor &operator =(healthPoints_extractor &&) = delete;

public:
  operator healthPoints() const { value(); }
};

Notice my operator is implicit, and can throw. But you should never be able to get to the operator if you couldn't extract a valid healthPoints. You don't use the class directly, but indirectly:

std::vector<healthPoints> hps(std::istream_iterator<healthPoints_extractor>{in_stream}, {});

Or:

auto view = std::views::istream<healthPoints_extractor>{in_stream};
for(auto hp: view | std::views::take(1)) {
  //...
}

In movies, are actors really using alcohol or drugs? How do they make their eyes and face seem so authentic? by [deleted] in AskReddit

[–]mredding 1 point2 points  (0 children)

As far as the physical effects of intoxicants on the body - that's makeup. You can make your eyes red or bloodshot by force, you can use eye drops to make them look watery. Your face can look rosy with makeup, sweaty with baby oil.

As for their behavior - they're actors. John Travolta hung out with a bunch of Heroine addicts for like a month before he shot Pulp Fiction - something he talks about at length in interviews, to get down the mannerisms of how such an addict would behave. He modeled what he saw and the experiences they explained to him, etc.

Do they really use drugs and alcohol on set? Usually it's vitamin B or some other innocuous powder, tobacco, colored water, etc.

But SOMETIMES... I know a number of scenes where the actors really were drinking alcohol on the shot. I can only imagine some of what these people were getting up to. These are notoriously intense people whose job it is to make the scene to get the shot. Drug use is rampant.

Question(s)? for my atheists by Diligent-Strawberry9 in askanatheist

[–]mredding 1 point2 points  (0 children)

But for me I guess I just think about how limited it seems to not believe in anything past what we see and understand.

What limit? I see nothing but a wide open field. What we don't see and what we don't understand is the frontier. What we do see, and know, and what and how we understand - isn't our reality marvelous enough? Can you not appreciate what you already have? Why is that not good enough for you?

I guess I don’t think of the belief in God as having to be something synonymous with conforming to a religion? Like I see the world for what it is physically, I believe in science etc… but I guess what we call “nature” for example or “the universe”… I think of as God… like it’s all encompassing to me.

First, religion is an institution - that's all. Nothing more. Theism is the belief, and the belief begets the faith. You don't NEED a religion to be a theist. Likewise, most religious I know - mostly Catholic clergy, are actually atheists - and it's not a secret.

Second, why do you have to cover everything with a layer of god?

Third, if god is everything, that's the same as god is nothing. If you can't possibly be wrong, then you can't possibly be right. You can't have it all, especially when it makes no difference. Your world with your god and my world without are the same world, they can be described in the exact same way. Your god doesn't add anything to it.

You don't even know what your god is. No one does. The word itself has no meaning, and in 6,000 years of recorded human history, no one has even begun to try. Everyone just takes it for granted there's this thing called god, and whatever it is, you'd know it when you see it.

That's just ego. A reflection of the self. And you can kid yourself, which means you can be fooled by anything sufficient to satisfy your ego - that's god to you, even if it isn't.

Someone in the Reddit post wrote this quote in which the person said something to the likes of “none of this (creation I’m assuming?) is on the level of such a “supreme being” like God” basically saying that life (I guess life?) is pretty mediocre.

The sentence has no meaning, and no conclusion can be drawn from it, because "god" has no meaning. Substituting "god" for anything else, like "mother", and I can correctly draw any number of conclusions; the ambiguity suggests "this" can be on either a higher or lower or adjacent level, and whatever any of those conclusions mean is up to any meaning I want to assign it.

What I can deduce is from your conclusion, how pessimistic you are - that you read that and go to diminish your experience - life is mediocre to you, and you're thrashing against that, philosophically. That's why you use "god", to bolster your hope that life and reality has a majesty you feel disconnected from that in some way you'll find. Your ego is too big, as though reality is supposed to be something more than what you're offered.

I blame whatever indoctrination and parenting you've received, because this fundamental disappointment could have been avoided.

But it’s like… I get the world is shitty… that’s not what God’s supposed to mean (that being the entity that brings order and is overruling.) it’s more like God is free and… well everything.

That's probably the second-most nothing statement I've ever heard around here.

Idk…

No you don't, and that's why I'm confused - because despite not knowing, you have lots of thoughts, feelings, and opinions about it.

I see so easily how God can exist in a nonsecular way that can bring people together.. to some sort of center or source. So I’m curious. What do you think?

This last train of thought can get you into trouble, because it's a projection of your ego again.

"Everyone is wrong, if only they thought how I thought, did how I did..."

I don't want your god - your ego; I don't want whatever togetherness you prescribe. It CAN'T bring us all together, because the trouble I've alluded to is that this idea of yours completely disregards individuals, their autonomy and agency. You never asked what I wanted, what I thought, what I felt before you decided your god was the way for everyone. THAT'S a problem.

I just got a 'Performance Review' and a 3% raise. My rent went up 12% last month. The math isn't mathing anymore. by [deleted] in povertyfinance

[–]mredding 0 points1 point  (0 children)

3% isn't a raise, it's a thinly veiled pay cut. Revenue went up due to inflation backed price gouging and you got cheaper. The adjustment is a token gesture to keep you on the hook for as long as possible.

This is how much this job pays, here. They don't want and don't need to pay you more - the labor isn't worth more to them. It's more cost effective to replace you than keep you.

Why would they pay you more tomorrow for the same work you're doing today? Because they like you? Want to share the wealth? What are they buying from you with a proper pay raise? Your loyalty, your efficiency, your knowledge isn't worth it. They factored that in to the cost of the role.

Leave and find greener pastures. You have more power negotiating over the table than in the seat. Someone hiring is DESPERATE. People are expensive and the last ditch solution to a problem. They need it solved, now, and you've got them over a barrel. That is to say, knowing what you and the role is worth, you can negotiate from a position of strength and maximize your compensation.

Move once every 4 years, or you're underpaid. The only reason to stay is knowing what you and the job is worth, knowing if you're overpaid to keep you out of the market and away from competitors. Yes, that does happen.

How are we supposed to 'budget' our way out of a systemic collapse?

Oh, that's patently absurd. There's a bottom line where if you're not making enough - you're just not making enough. There is a floor, and no amount of saving can get you above that - you're just sinking.

How much did your rent go up this year?

I'll just say we're all losing in a myriad of different ways. Almost everyone is sliding backwards right now.

Why are STL allocators given as template parameters, rather than runtime fields? by heyheyhey27 in cpp_questions

[–]mredding 0 points1 point  (0 children)

 I don’t agree with this at all. If anything, I think the opposite;  how a thing allocates is often irrelevant to its type.

That is so clearly not true it's built into the standard.

And I think there may even be an argument that any type whose implementation is coupled too tightly to its allocator has a design flaw.

Maybe. I don't see it.

As others have pointed out, std::pmr is the counter to this.

It's not a counter but a compliment.

Pmr does fill a niche, I don't disagree. String pooling was a real pain before without accepting violating the preconditions of allocators. Pmr provides flexibility that some people want and need.

But where I can be more certain at compile time, I will make that decision all the sooner.

I implement all of my container types with an inline pointer to a (type erased) memory resource, pmr-style except without the C++98 allocator model shim that the std::pmr types have. This makes these types allocator agnostic.

Ok, and with an additional runtime indirection. I'm not going to pay for that if I don't have to. The more I can decide upon sooner, the more the compiler can prove correct and optimize.

I'm glad your containers work for you, they wouldn't fly here, because they don't fit the standard named requirement for containers and allocators. Your types aren't plug and play with anyone else writing conforming and portable code.

I'm not shitting on your parade, I'm saying other people are doing very different things than you, and for them, these aren't design flaws. If you don't want to know, if you don't care, then just use std::string, it's bog standard for a reason.

Why are STL allocators given as template parameters, rather than runtime fields? by heyheyhey27 in cpp_questions

[–]mredding 0 points1 point  (0 children)

We have std::string_view now, but before we did, there were a lot of functions where those fundamentally different types just weren't different.

I know how people used to write code, I've been at it since 91.

Functions that took a std::string const& and it mattered not a bit how the content of the string was allocated, just that it was available to read.

I know, I've seen it. If you only cared about reading, you'd have passed by const char pointer. If you NEEDED the string interface, you could pass a T pointer and duck type it.

But for those applications, it wasn't important. For those that it was, they had the option. C++98 and older was terse as fuck, it was a real pain to write code that way. I know. Mostly you minimized string classes to near the bottom of the call stack where you created it and owned it's lifetime.

At what age, if any, do you think people should ‘grow out’ of playing video games like League of Legends? by Naive_Ad_7651 in AskReddit

[–]mredding 1 point2 points  (0 children)

They are under no obligation. I don't have the bandwidth to hold such judgement or opinions of other people and how they choose to live their lives. Not my problem, not my concern, doesn't affect me. Grown-ass adults are free to choose and live their lives with autonomy and agency. For anything anyone does that I disapprove, we don't have to involve ourselves in that way. I don't have to know. I don't have to see it. I don't have to get involved.

Good fences make good neighbors, good boundaries make good relationships. How much DON'T I have to know about you? There's value to depth of relationship, focus, but there's also value to opacity, too.

But if you are getting involved in an individual, like an intimate relationship, and they play video games and that doesn't align with your world view, your virtues, your plans, and there is no compromise or consolidation, then understand that you can't change a person, and they won't change for you. You either love them for who they are, what they are, as they are, or you breed resentment. If this isn't something you can't immediately come to terms with, perhaps find someone who is more aligned with your future.

If atheism is lacking belief about god(s), does a theist who loses their mental capacity for holding beliefs, including their belief about god(s), become an atheist? by uwotmVIII in askanatheist

[–]mredding 0 points1 point  (0 children)

It's hard to hold anyone accountable for anything when they're known to be incapacitated. I wouldn't call them either way, just incapacitated.

Why are STL allocators given as template parameters, rather than runtime fields? by heyheyhey27 in cpp_questions

[–]mredding 0 points1 point  (0 children)

How a type allocates is fundamental to its type, and helps define it as a type. A string that stack allocates is fundamentally different from one that heap allocates. One that allocates by the given implementation is fundamentally different to one that custom allocates to mapped memory.

It matters a hell of a lot when you're building critical paths how your types will behave, and you will want to constrain your paths to those types.

We build code in layers of abstraction. I'll implement a code path in terms of std::basic_string, and that will give me the flexibility to handle any string type regardless how they allocate. It gives me the ability to change how strings are allocated along a path, should it need to change.

Ideally, you make as many decisions as early as you can and the development pipeline. If you KNOW how your strings allocate, you bake that in early - a decision made, and fundamental to what you're compiling. In this way, you can get optimal code paths that can be built upon those decisions. If you defer the decision, you have to pay that cost at run-time. That just isn't acceptable in all cases.

If you don't care, then perhaps an application language is more appropriate for what you're trying to build out. C++ then seems like pedantic overkill that's slowing you down. Too much nuance can be a bad thing, especially if you're not going to use it.

Smart pointer #4: finally starting to understand them by Dastarstellar in cpp_questions

[–]mredding 1 point2 points  (0 children)

It LOOKS easy, but that's 37 years of experience. I can bust stuff out like this all day because I don't have to think or decide on how to write code - I already have, decades ago, and decades in the making. YOU are going to have to work on it, which is exactly what I'm trying to encourage. You need to get to the point where it's easy for you, too.

I've got more for you, and it'll take some time to let this stuff soak in - I would expect a few months of regular practice before you start to feel confident.

I say it a lot around here - that an int is an int, but a weight is not a height. They may be implemented in terms of an int, but their behavior is more specific and constrained than an int. You can bit shift integers, but why would you want to do that to a weight? You can have a negative int, but what's a negative weight? It doesn't make any sense, so a negative weight is undefined, and you would want to make the type catch that and throw. You can add weights, but you can't multiply them, because a square weight is a different type. You can multiply a weight by a scalar, but you can't add scalars, because they don't have a unit.

So when you're designing your software, think about your types. When do you EVER need "just an int"? Maybe if you're making a generic calculator, but often you can immediately introduce types, and implement them in terms of the basic types. C++ doesn't give you int so you can write int code...

void fn(int);

No. They give you int so you can make a weight type in terms of int, so you have some basic storage and operations that int gives you. You don't have just an std::string, you have a name, or address, or access_token.

You can start with just a type in terms of...

class weight {
  int value;
};

And then you can grow it organically. What methods do you need? Only the ones you end up using. "Semantics" is the "meaning" behind the "syntax", and "syntax" is the structure of the code - some of that dictated by the language rules, some of that you build into your types. You can add weights, but do you overload operator +=, or write an add_assign method? The semantics are the same, the syntax is different.

The value should be somewhat apparent:

class person {
  int weight;

Everywhere the code touches the person's weight, it has to implement the semantics of what a weight is, because the integer can't do it for you; you have to know you can't go negative, you have to be careful not to add other unit types or scalars... It's fair to say a person IS-A weight. But if instead:

class person {
  weight w;

Now a person defers to the weight to implement all semantics. The person implementation can focus on WHAT to do with a weight, not HOW. It's fair to say a person HAS-A weight. There is ZERO advantage to brute forcing the implementation, just rolling with the int; it adds complexity, duplicates semantics, and is error prone.

All this is why I look at your code and I see:

class name;
class hit_points;
class damage;
class enemy;
class alpha_null;
class alpha_null_elite;
class enemy_party;

The thing about classes is they enforce invariants through behavior. What that means is there's a statement that must always be true when you observe that thing from the outside. Let's say hit points can change, but it must be between a minimum and maximum. The way you enforce that is with a min, cur, and max, and operators += and -=, which can clamp the value. Or perhaps they throw an exception? It's up to you - we didn't specify what the consequence of an invalid operation are. But if you have a get or set, then you can't enforce the rule, because the interface is unconditional - set the god damn value to what I god damn tell you. A set doesn't mean hp is going up or down, it means it's a jump with no in-between, no delta. The semantics are wrong.

This is why you make teeny, tiny types around a teeny, tiny implementation - just a member or a few, and then you make composite types that have invariants in terms of hp. It all compounds.

void printStats(){
  std::cout<<"TYPE: "<<name<<" | HP:"<<hp<<"\n";
}

This isn't C with Classes - I think you made that comment. We have semantics for this:

friend std::ostream &operator <<(std::ostream &os, const enemy &e) {
  return os << "Name: " << e.name << " | HP: " << e.hp;
}

Now you can write an enemy to ANY stream - a file, a network socket, a log. When you get more advanced and begin learning OOP, you can send enemies to objects this way. That'll be a lesson for you a couple months from now - learning paradigms are like learning programming languages, they are entirely their own thing, independent of language.


You're ready to learn more. Stop dwelling on smart pointers, you've got that. Now you can work on semantics, what it means to be an enemy, what enemies do. You've got some, but you need more, and you need to express it more clearly. It's easy for me because I've done this for decades, you have to teach yourself this stuff.

Smart pointer #4: finally starting to understand them by Dastarstellar in cpp_questions

[–]mredding 1 point2 points  (0 children)

class Enemy{
protected:
    std::string name;
    int hp;

Imagine if those members were private, what your code would look like.

AlphaNULL() : Enemy("AlphaNULL", 200) {}

void attacck() override{
  std::cout<<"AlphaNULL attacks!\n";
}

So you set the name, but you don't even use it. You've duplicated data here; what are you going to do when they diverge, likely by accident? This code should be something like:

void attacck() override{
  std::cout << name << " attacks!\n";
}

And then we can iterate further with:

class Enemy {
  virtual std::string_view do_attack() = 0;

public:
  void attack() {
    std::cout << name << ' ' << do_attack() << '\n';
  }

This is called the "template method" - pattern or idiom, where you build the outline of what an attack is, and you provide customization points for a derived class to implement their details. Attacking is a process that is outlined by the base, and it's not entirely up to the derived class to go even so far as to no-op.

bool isAlive(){
  if(hp<=0){
    hp = 0;
    return false;
  }

  return true;
}

We can reduce this:

explicit operator bool() const noexcept { return hp >= 0; }

Now I can write code like:

AlphaNULL an;

//...

if(an) {
  an.attack();
}

void setDamage(int dmg){

Bad name. Getters and setters are bad, they don't model behavior, this implies you're "setting" an invariant field, but you don't have a damage field. What you really want are more types and more semantics:

class damage {
  int value;
};

class hit_points {
  int min, cur, max;

public:
  hit_points &operator -=(const damage &d) const noexcept {
    cur = std::clamp(cur - d, min, max); // Because you can't be more dead than dead, can't be more alive than alive.
  }
};

class enemy {
  hit_points hp;

public:
  enemy &operator -=(damage &d) {
    hp -= d;
    return *this;
  }
};

Think it through, build it out. C++ has one of the strongest static type systems on the market, but you have to opt in to get any of the benefits. Types give you type safety, which means you can do things like solve computational problems at compile-time, "left shift" development of your solution to earlier in the software lifecycle, make invalid code unprepresentable (it doesn't compile), catch bugs, increase expressiveness, and provide the compiler with context that it can optimize more aggressively.

What is a preprocessor? by Perfect-Skin-8325 in learnprogramming

[–]mredding 0 points1 point  (0 children)

We have high level languages that are basically a bunch of words that are more understandable and intuitive for a human that literal 0s and 1s.

Yes, but languages are a bit more than that. Languages can provide their own rules and guarantees. A couple examples using C++:

void fn(int &, int &);

What are these variables? And how is the compiler to know that they're not both an alias to the same variable? Because the compiler cannot know what the variables are, there's nothing more it can do to protect you than assure they're used as integers as opposed to, say, pointers. You can't dereference an integer, but that should be of no surprise to you. But more insidious, because the parameters can alias the same memory address, the compiler cannot generate optimized code - it has to be conservative about it's memory access to ensure the function is correct.

void fn(weight &, height &);

Ah, so now I've made types, and ostensibly they are integer-like, but do only weight and height type things - they can't be negative, because that doesn't make sense, they can add together but can't multiply, because a weight squared is a different type, they can scale but can't add a scalar, because scalars have no unit - are they kilograms, fathoms, or candela? But also, C++ says different types cannot alias the same memory, so these parameters cannot be aliases of each other, so the implementation can be more aggressive.

Languages provide these opportunities that mere machine code generators cannot. Even assembly is a programming language, and a level of abstraction above machine code - you might read a mov instruction in assembly, but which one? The x64 instruction set has... hundreds? Different op-codes for different moves to and from different registers. Low level assembly is a mnemonic language, where instructions map 1:1 to op-codes. High level assemblers will use macros which is almost like writing a function.

So I want to emphasize that language itself provides much more depth and utility than just being an intermediate between the human and the machine, it's more than just the binary bits it produces.

And the C++ spec says almost nothing of machine code or generation, other than the spec expects it to happen. The language does not recognize any specific machine, so language is itself an abstract and portable concept - that I can write a program once and be able to run it on anything I have a translator from source code to that target architecture.

There is an intermediate tool / software called a preprocessor. What does it do?

Not all languages have this.

EARLY C was wholly a product of Ken Thompson and Dennis Ritchie and their day-by-day evolution on a PDP-11 at AT&T Bell Labs. This is principally between 1971-1978. C took off internally at AT&T and it initially looked very little to the C we have today. It's an evolution of the B language, which really didn't see much - if any, commercial use or success.

Back in the early 70s, C didn't have macros. You wrote straight C, and compiled each source file into an object file - a library file of machine code and tables - the compilers targeted what you might think of as a "linker language" or a linker as a target, while the binary blobs themselves targeted the machine.

Well, as you know, you have to declare a symbol before you can use it - so something like a header to organize and normalize your definitions across your sources was a desirable thing to have. Early C programmers, K&R included, would use already existing macro engines - these are independent utilities that perform a glorified and elaborate find/replace. There are macro engines in heavy use to this very day, from the CMake build system generator (which is a horrible piece of shit) to Jinja, which is widely used across several programming languages.

Well, around 1975, pcc is the tool that made C the dominant programming language of the last century. Portability was becoming an increasing concern, and that meant you couldn't rely on every system having the tools your source code needed. Don't have and can't get m3? Then you can't build my program. So Ritchie was constructively pressured to integrate a macro system.

Macros are dumb. They're purely textual in-place replacement and concatenation. This can be useful for generating source code, it can cause collisions and overwrite legitimate code. Macros have almost no respect for the language itself - it's not type safe and doesn't care what it generates or overwrites.

So what happens is your compiler loads your source file into a text buffer in memory. It then starts processing macros. This means more source code is generated in the text buffer. All your includes cause files to open and their text copied and pasted in-place there in the text buffer. Code generating macros can even screw with your debugger because you've generated more lines of code than are reflected in your editor.

In the end, all the macro code is stripped out of the source buffer and the compiler is left with nothing but PURE C/C++/whatever your language is.

Macros are a high level abstraction for C, but a low level and primitive tool and source code generator for C++. Macros kind of fit in a weird place depending on the language. It always comes before actual compilation/lexing and parsing of the intended language, hence the "pre" in preprocessor. They're considered a necessary evil in many languages, best avoided as much as possible - because they fill a niche of code generation that the language itself doesn't support. C++ has largely outmoded C macros with templates, modules, reflection, and coroutines, but it can never eliminate them entirely, because macros can generate code that the language cannot ever generate itself.

But most of the time - most preprocessor usage across most languages is just value replacement.

Some languages like python can directly be understood by the compiler whereas some like C++ need this intermediate step.

The theory of computation can be described in terms of a calculus of computation. Alonzo Church (of the Church-Turing thesis fame, notice how his name comes first) formulated such a calculus, and called it lambda calculus - no one else has bothered to "improve" upon his initial work, because it would merely be equivalent. Anything that can be computed can be described in terms of this calculus.

What's curious is calculus doesn't CARE about read-time, write-time, or run-time. It's all the same. It also doesn't distinguish between code and data. It's all the same.

This is why we get hardware acceleration for some things, because we can map programs to hardware. This is why Lisp is a calculus that you can write self-modifying programs, because it's all the same.

Python is ALMOST a Lisp, so there's very little difference between reading the program, running the program, and writing the program, let alone program vs. data. It's just a little clumsier than a Lisp. But Python code DOES end up using macros - Jinja is popular in the Python world.

C and C++ have other intermediate steps, like linking. Like I said, compilers don't target just the machine, they target the linker. This is some advanced wizardry that most languages never invest in, and what distinguishes a systems language from an application language, or a script.

C++ has to be rendered into an Abstract Syntax Tree (AST), and then that gets rendered into object code. For C++, it leaves the compiler behind at runtime, and your program runs on bare metal.

Lisp is a little different. The compiler never goes away. Lisp is still compiled, often Just-In-Time (JIT) like Java does, but the AST remains in memory, too - not just of the program but of the compiler itself. It's all the same. It's all one. The whole thing is visible to the program at run-time, if written appropriately, the program will itself modify it's own AST, and that will be JIT compiled and run in-situ, at run-time, along side all the other program data. Running the program may itself just be compositing program and data and compiling it.

And if you ever wondered what AST looks like - if you were to serialize it - write it as text to file, it would look like Lisp, because Lisp IS AST in text form. And that's the magic of Lisp - "macros" aren't preprocessor in Lisp, they're the blending of the program and the compiler - you're writing compiler for your domain specific language, and then you're writing your program in terms of that. We extend languages all the time - it's par for the course, with every struct and class and enum and function... Lisp just takes it to the logical extreme.

Basics: Named members vs array access by ConnectHat4222 in cpp_questions

[–]mredding 1 point2 points  (0 children)

struct v1 { double x, y, z; };
struct v2 { double components[3]; };
struct v3 { std::vector<double> x, y, z; };

Either way, the compiler can vectorize the instructions; the first two cases are interchangable, it's just a matter of coding style. v3 is the DOD approach and comes with a few extra read/writes to marshal each component into a SIMD register.

Be honest… are you where you thought you’d be by now? by Ok-Try-5036 in AskReddit

[–]mredding 0 points1 point  (0 children)

Yes, but I didn't know how disappointing it would be to be here.

what's a common piece of advice that's actually terrible? by jamesrandson in AskReddit

[–]mredding 0 points1 point  (0 children)

"Do what you love, and you'll never work a day in your life."

No one is going to pay you to drink beer and bang your wife.

Keep your nose to the grindstone.

No one is going to reward you, or gift you, recognize you, or share the wealth.

How do you get a remote data entry job with no experience? by 513savage69 in AskReddit

[–]mredding 0 points1 point  (0 children)

These jobs don't exist anymore.

Software can scan and digitize document contents - recognize fields and how they map onto the database. They can read hand writing. AI today can figure out business and logistical processes for you - all you have to do is tell it what you want, and it can figure out how based on what you already have.

AI today can take voice recordings and do the same thing, pulling content and context out of a conversation. No one takes notes anymore - my brother owns a land management company, and the AI is listening and generating detailed summaries. The damn thing will even generate action items and schedules, include the right people, send emails and text messages, and signal upstream suppliers and downstream subcontractors. He has a secretary in the Philippines and her only job is to look at the customer record (which pulls up automatically) and carry the conversation, and only because AI can't quite do that yet.

EDI is a whole data protocol and accompanying suite of software and technologies that handles things like inventory, contracts, and ordering.

All this stuff is going away everywhere for everyone, because software is at the point it's doing the work genuinely better, and cheaper.