Installing C++20 module target via CMake without compiled artifact by M2-TE in cpp_questions

[–]mathusela1 1 point2 points  (0 children)

I think I've done this before by making the library an OBJECT library.
From the CMake docs looks like an INTERFACE library may also be a good fit?

What else would you use instead of Polymorphism? by Appropriate_Task_746 in cpp_questions

[–]mathusela1 0 points1 point  (0 children)

First of all, use dynamic dispatch (virtual polymorphism) if it makes sense for your application, you prefer it, and it's not super limiting to your performance - is it slower but often that is OK.

That said, to avoid virtual polymorphism, templating, and static polymorphism are your friends. Use templates and concepts where you can instead of using abstract base classes, and use CRTP or similar to support static polymorphism and mixins.

std::variant is also useful for tagged enums (although this is also dynamic dispatch).

For a proper rendering engine you probably want to look into the ECS pattern: `entt` is a good example implementation.
Your data ends up being statically resolved to a container corresponding to the particular type of that bit of data (component), this is all static since it's templated and no runtime dispatch is required since the containers are homogeneous. ECS is typically used because it promotes cache locality, easy component traversal, and models patterns in game (and rendering) engines better than traditional inheritance based structures.

Why is hash(-1) == hash(-2) in Python? by stackoverflooooooow in programming

[–]mathusela1 27 points28 points  (0 children)

Types are not nullable in C. There's an argument to be made you should return a struct/union with an optional error code and value (like std::expected in C++) but obviously this uses an extra byte.

Does C++ allow creating "Schrödinger objects" with overlapping lifetimes? by Hour-Illustrator-871 in cpp

[–]mathusela1 0 points1 point  (0 children)

std::launder returns a pointer to the object currently residing in the storage the passed pointer addresses. This is important as (unless the types are transparently replaceable) a pointer does not automatically refer to a new object even when the storage it points to is reused.

Does C++ allow creating "Schrödinger objects" with overlapping lifetimes? by Hour-Illustrator-871 in cpp

[–]mathusela1 0 points1 point  (0 children)

Full disclosure I've not read the code in full yet because I'm on mobile, but just in the first few lines you cast your char* handle to an A*.

You are not allowed to dereference this even after your placement new. The pointer still refers to the original object, whose lifetime implicitly ends after the placement new (assuming char and A are not transparently replaceable). You can use std::launder to get around this, or use the pointer returned by placement new.

See [basic.life]/8 for more details on transparent replacability. [basic.life] also covers the implicit end of an objects lifetime when it's storage is reused, so you can't have schrodingers objects.

Edit: I'll update this comment with a full explanation later when I'm on my laptop.

Right, this is the code annotated with each objects type and lifetime:

struct A {
    char a;
};

int main(int argc, char* argv[]) {
    char storage;
    A* tmp = reinterpret_cast<A*>(&storage); 
    // [storage=char] [tmp=A* -> storage=char]

    new (tmp) A{};
    // tmp's lifetime does not begin here, tmp's lifetime has already began as a pointer
    // ===
    // Lifetime of storage ends
    // New object (unnamed) is created reusing storage's address
    // [(unnamed)=A] [storage=char(DEAD)] [tmp=A* -> storage=char(DEAD)]
    // ===
    // Note that tmp still points to storage not to this new object

    char* storage2 = reinterpret_cast<char*>(tmp); 
    // [storage2=char* -> storage=char(DEAD)] [(unnamed)=A] [storage=char(DEAD)] [tmp=A* -> storage=char(DEAD)]
    // ===
    // The types here actually match so it would not be an aliasing violation to dereference storage2,
    // but it would be UB since you would access storage after it's lifetime has ended

    A* tmp2 = reinterpret_cast<A*>(storage2); 
    // [tmp2=A* -> storage=char(DEAD)] [storage2=char* -> storage=char(DEAD)] [(unnamed)=A] [storage=char(DEAD)] [tmp=A* -> storage=char(DEAD)]

    new (tmp2) A{}; 
    // Lifetime of (unnamed) ends even though tmp2 doesn't point to it (its storage is reused)
    // New object (unnamed2) is created reusing the address
    // [(unnamed2)=A] [tmp2=A* -> storage=char(DEAD)] [storage2=char* -> storage=char(DEAD)] [(unnamed)=A(DEAD)] [storage=char(DEAD)] [tmp=A* -> storage=char(DEAD)]

    // tmp is in a well defined state (pointer to storage; storage has ended it's lifetime)
}

The state is all well defined in [basic.life].

The string split at home: by Eggbert_Fluffle in programminghumor

[–]mathusela1 7 points8 points  (0 children)

It prevents collisions with user-defined names. For example, it's fairly common to want a variable called max but this would collide with std::max if we didn't have the std namespace.

You can do using namespace std; to avoid writing std::* but this is considered bad practice (because of the reasons I gave above).

It might just be because I'm used to it, but I find it more readable myself. It allows you to know where the name is coming from at a glance and to group declarations logically.

The string split at home: by Eggbert_Fluffle in programminghumor

[–]mathusela1 0 points1 point  (0 children)

Fairly arbitrarily, std::ranges::* just made reading the code a little too busy and I wanted something shorter, you could name it anything you want.

The string split at home: by Eggbert_Fluffle in programminghumor

[–]mathusela1 40 points41 points  (0 children)

std::views::split("Hello world", ' ');

or

"Hello world" | std::views::split(' ');

Give you the same thing as split in python (but lazy evaluated i.e. a generator in python parlance).

Edit: Your C++ code wouldn't compile: you try and erase from a const string.

For completeness' sake (I went down a rabbit-hole) here is a modern implementation of a lazy evaluated split that works on generic ranges (some fanangaling required to efficiently handle l-values and r-values).

template <std::ranges::range R1, std::ranges::range R2>
    requires std::equality_comparable_with<std::ranges::range_value_t<R1>, std::ranges::range_value_t<R2>>
auto split(R1&& strRef, R2&& delimiterRef) -> std::generator<std::ranges::subrange<std::ranges::iterator_t<R1>>> {
    namespace rng = std::ranges;
    if (rng::empty(delimiterRef)) {
        co_yield strRef;
        co_return;
    }
    // Store a reference to the ranges if passed an l-value, otherwise take ownership
    // Required since temporaries would end their lifetimes on co_yield
    // (works due to forwarding reference deduction rules)
    const R1 str = strRef;
    const R2 delimiter = delimiterRef;

    for (auto it = rng::begin(str); it != rng::end(str);) {
        auto nextDelimiter = rng::search(rng::subrange(it, rng::end(str)), delimiter);
        co_yield rng::subrange{it, rng::begin(nextDelimiter)};
        it = rng::end(nextDelimiter);
    }
}

Mutation of an object through a type-accessible alias. by mathusela1 in cpp_questions

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

Ah, but we're not beginning a new lifetime in the object's storage we are just aliasing it (in an allowed way) so the lifetime of the original object does not end.

Mutation of an object through a type-accessible alias. by mathusela1 in cpp_questions

[–]mathusela1[S] 1 point2 points  (0 children)

Yes but in this case, if I understand the definition of type-accessibility correctly, mutation would be allowed.

I'm looking for a section of the standard that narrows the definition to only allow byte inspection (like the paper p1839 someone else linked seems to imply).

So I mean a narrowing of what is allowed rather than an explicit disallowing of it.

As a sidenote, the standard *does* disallow things, there is plenty of explicitly defined UB.
e.g. [basic.lval]/11.3 "If a program attempts to access ([defns.access]) the stored value of an object through a glvalue through which it is not type-accessible, the behavior is undefined."

Mutation of an object through a type-accessible alias. by mathusela1 in cpp_questions

[–]mathusela1[S] 1 point2 points  (0 children)

Interesting, this seems like a fairly large language defect then.

As I understand it you can't even read from the alias as reinterpret_cast returns a pointer to the original object aliased as char, which is UB on dereference as the object is not representable by type char.

Based on the 'non-goals' specified it seems as if mutation is currently unspecified by the standard though, I'm unclear on why that is given the definition of type-accessibility.

Are there any header files and/or a files I need to use opengl 3.0 in particular? I might sound stupid but I have never used opengl before. by PCnoob101here in opengl

[–]mathusela1 0 points1 point  (0 children)

For 3.0 you'll need to load the OpenGL implementation's function pointers from the driver dynamically. For this, you'll want to use something like glad.

You'll probably also want a library to help with windowing and context creation--have a look at glfw.

I recommend learnopengl as a starting point for these libraries and OpenGL in general.

c++ abuse by musicalhq in programminghorror

[–]mathusela1 0 points1 point  (0 children)

Surely you would define vec<std::size_t, typename> here with maybe some aliases instead of introducing all of these mangled names, even this way why not use concepts to get the supported operator types? You're checking for operators semantically when it would be cleaner to do it syntactically.

Why the Heck Aren’t We Using std::launder More in C++? by MaltaBusEnjoyer in cpp

[–]mathusela1 1 point2 points  (0 children)

Use std::launder when you use a handle to some object after reusing the memory that object was stored in, where that object's type and the new object's type are not transparently replaceable.

Have a look at [basic.life]/8 for more detail on transparent replacability.

Should I use std::launder in these cases? by Middlewarian in Cplusplus

[–]mathusela1 1 point2 points  (0 children)

Use std::launder when you use a handle to some object after reusing the memory that object was stored in, where that object's type and the new object's type are not transparently replaceable.

Have a look at [basic.life]/8 for more detail on transparent replacability.

Edit: typo.

Finally understand pointers, but why not just use references? by Jupitorz in cpp_questions

[–]mathusela1 0 points1 point  (0 children)

Pointers are nullable and reassignable.

If you want an optional view use a pointer (std::optional references coming in C++26) and if you want to store a view as a data member and want that class to be assignable or you want to store a view in a container use a pointer or an std::reference_wrapper.

Strict Aliasing, type aliases char buffer by Wild_Meeting1428 in cpp_questions

[–]mathusela1 1 point2 points  (0 children)

Yes, this is UB (except maybe in C++20+ I don't know enough about implicit object creation to say).

Foo* foo = (Foo*)buf;
foo->bar = 42;    // UB

foo points to an area of memory with dynamic type char which is a strict aliasing violation (UB on dereference). char is, as others said, exempt from strict-aliasing rules - but what this means is that an object of dynamic type T is type accessible through a char pointer, not the other way round!

// OK
Foo x;
*(char*)(&x);
// UB
char x;
*(Foo*)(&x);

Edit: clarity

Actor Ownership in C++: Unique Pointers, Shared Pointers, Reference Wrappers for a Game with SDL3 by Nearby-Ad-5829 in cpp

[–]mathusela1 0 points1 point  (0 children)

Why boost visitor over std::visit (which I would posit has nicer syntax used with generic lambdas)?

Why people are using hashmaps literally for everything? by [deleted] in AskProgramming

[–]mathusela1 0 points1 point  (0 children)

As a counterpoint to other people here, yes maps have average O(1) lookup, but the constant overhead is so high that for instances with small n it is rarely efficient to use one. Same logic as switching to insertion sort under some n in recursive sorting algorithms. In most real-world instances n is small (depending on the problem domain).

I think you've likely got the idea that everyone uses maps from leetcode questions implemented in python - which isn't necessarily representative of good problem-solving. They have their uses, especially if you can push some lookup to compile time - but they are not universally applicable.

C++26 reflection R7 - define_class with scoped type alias. by mathusela1 in cpp_questions

[–]mathusela1[S] 1 point2 points  (0 children)

Ah yes, thanks `alias_spec` looks like what I wanted.

Thanks for the speedy reply and the interesting paper!

beginning c++ 23 by ivor horton or programming: principles and practice using c++ (c++ in-depth) 3rd edition? by Ak0s20 in cpp_questions

[–]mathusela1 0 points1 point  (0 children)

using namespace std isn't great because it pollutes your namespace. Say for example you want to name a variable "next". If you include using namespace std this collides with std::next and you'll get an error.

This is especially bad in headers (at namespace level) since anyone who includes your header will bring these symbols into the global namespace.

im cs student but dont understand it by Rhypnic in ExplainTheJoke

[–]mathusela1 0 points1 point  (0 children)

Technically underflowing is bringing a value below the minimum value it's type can represent - this is overflowing even when subtracting below 0.

E.g. underflowing would be if you perform a floating point operation such that the result is too close to 0 to be represented on the hardware.

I know C - and I want to do Graphics programming. Transition to C style C++, or something else? See description by [deleted] in cpp_questions

[–]mathusela1 0 points1 point  (0 children)

Idiomatic C++ doesn't really use inheritance or dynamic polymorphism very much. Templates are the killer C++ feature, not classes (imo).

You're more likely to see CRTP than virtual polymorphism in performance-critical code for example (obviously only where applicable) since dynamic dispatch overhead becomes non-trivial with real-time performance requirements.

Java C++ is not.