pouët.net

Modern C++ in demos

category: code [glöplog]
After being on C++98/03 for decades, I've recently picked up more and more modern C++ (post C++11 that is) in order to improve my general programming skills. I've used my demo engine as a test project for a lot of the features and I've found it rewarding, but there's a bunch of stuff in the language that I can't really find any use for when it comes to demos. What I'm talking about is the variadic template stuff, template metaprogramming, constexpr and all that. Do any of you guys use those while coding demos or are they just for the financial guys, Herb Sutter and people writing libraries? I know graphics (and by extension demoscene) code tends to be on the conservative side, but any tips on applying those?

(also - if you're stuck in the past like I was, check out C++11 because it's fucking amazing)
added on the 2020-05-05 20:10:55 by Preacher Preacher
C++11 is also kinda in the past ;)

I've been using some modern C++ features, but not too much. I think modern C++ is getting a bit overly abstract at times. But there's absolutely a bunch of nice new features as well.
added on the 2020-05-05 20:31:19 by kusma kusma
Yeah, C++11 is in the past but it's the big change from the past and pretty much everything past that has been mostly refinements. I'm on C++17 with my current engine and it's amazing just how much cleaner some things become with things like std::variant, std::optional and proper lambdas. But I don't get the desire to turn it into an abstract functional programming language either.
added on the 2020-05-05 20:48:38 by Preacher Preacher
and no more asterisk shenanigans
I used modern C++17 in my demo Burn It Down!, most stuff was templated to switch out underlying arithmetic types and i have a type fixed<integrals,fractionals> which implements a generic fixed-point arithmetic type. need more fractionals? bump that number by one.

For constexpr: It allows the compiler to evaluate expressions at compile time, sometimes even partially. So vector arithmetic (in my case based on GLM + fixed<>) can be executed at compile time and you can create your lookup tables with that (and this needs variadic templates, sadly)

You can optimize your "base" of the demo by exchanging parts via templates, create a demo sequence by using a variadic template instead of an array to prevent virtual function calls and all that stuff. The main function is pretty much this:

Code:int main() { init_hw(); run<BootScreen, Logo, Raycaster, RaycasterTransition, StreetScroller, ...>(); }


Allows me to remove/add effects easily and due to link optimizations, my demo shrinks/gains size based on the effects declared in this single line of code

Another thing used were "magic textures":
Code: template<size_t W, size_t H> struct PACKED PalettedTexture { static constexpr size_t width = W; static constexpr size_t height = H; Color palette[256]; uint8_t pixels[W * H]; uint8_t get_index_unsafe(size_t x, size_t y) const { return pixels[y * W + x]; } … }

As i referenced all textures by name, i could utilize the template parameter to give the texture fixed sizes and just link them by symbol name. The compiler could then create efficient code for pointer multiplication and so on

Another thing for constexpr use is "Color": A 16 bit RGB565 color structure which i can just use by calling the constexpr ctor "Color { 0x43, 0x13, 0x43 }" and get a compile time calculated RGB565 value instead of having code emitted that calculates it
added on the 2020-05-05 21:42:22 by xq xq
i have rules. i need to be able to understand the code at 3am, in a dark room, maybe drunk. in such a context i might not remember language constructs that havent already been embedded within me for 20 years. therefore all these silly modern things are right out.
added on the 2020-05-05 21:51:06 by smash smash
I have the same rule (except I don't drink alcohol), which is why I use the language constructs so I can understand those once instead of having to understand my own strange code :-)

(I don't use every C++17 feature. _Far_ from it. But C++11 sure was a leap, especially around range-based for loops and unique_ptr.)
added on the 2020-05-05 21:54:45 by Sesse Sesse
(disclaimer: Saga Musix pointed me to this thread)

What i've noticed is that i mostly stick to C++98/03 despite most compilers i use nowadays support C++11.
When writing libraries (for other people to use, like these here) i try to stick to a plain C API where it makes sense, mostly because it's easy to wrap C++ around it if someone wants to, or interface with some other language like Zig or Rust.
If it has to be a C++ lib then C++98 without STL. I've noticed that if you just go and kill every problem with some STL container you're not going to actually think about how to efficiently solve a problem.

For my recent AVR demo thing i've been in the weird position of using GCC 9.2 with all those nice C++11-and-up features BUT NO STL. Argh. So ended up implementing those parts i needed from scratch: Some type traits, sort(), lots of template magic and force-inline to get the LCD communication flexible enough but as fast as physically possible, and various other things i forgot.

What annoys me about STL is that it assumes that new() is available, which is a bad idea on microcontrollers. And with lambdas and all those fancy, automagic things i can never be sure it's not new-ing something internally.
Plus including a bunch of STL headers like <algorithm> and some containers make your compile times go through the roof.

Things from C++11 that are (imho):

- really useful: constexpr, override, standardized <type_traits>

- moderately useful: auto (when auto-generating very complicated types based on a template expression it's ok, but it leads to general lazyness otherwise); move semantics (for performance); variadic templates (if you actually need those, you're likely overdoing it)

- bullshit: "modern C++"; auto and shared pointers; Rvalue vs Lvalue references (oh the confusion!); rules for when which class members are auto-generated (ctor vs move ctor vs copy ctor vs different kinds of assignments to preserve backwards compatibility. srsly who can remember all of this shit); lambdas (confusing ownership shenanigans; functors are easier and much more obvious but a bit more typing), std::launder (wtf!)

One of the big problems is that C++ uses copy semantics by default and then they retroactively tried to fix it by tacking move semantics on top.
Move semantics as the default plus ONE custom copy method if you need it is way more sane but unfortunately it's too late to fix this. Big plus for Rust in that regard.

Another problem is the <type_traits> stuff. It would be way nicer to be able to (at compile time) iterate your structs or classes or whatever and programmatically pull out whatever info you want, since <type_traits> is unlikely to cover all possible use cases one might have.

TL;DR: I'd like to move away from C++ because the entire language is turning into an incomprehensible clusterfuck and way too many rules to remember. It's a bit like OpenGL in perpetual compatibility mode.
added on the 2020-05-05 22:09:50 by fgenesis fgenesis
honestly I'm reluctant and a bit slow embracing new cpp feature. for now, constexpr, std::unique_ptr and std_functions are things i use in many cases and i feel comfortable with that. auto too, but I'm trying to be careful not to create situations where type confusion could become a thing. if i have a reference to a messy to write out type like Array<std::pair<blah,foo>>, auto might come in handy to make stuff actually more readable. I tend to prefer framework stuff over stl stuff in many cases (like JUCE or the cinema 4d sdk or whatever I'm working with). also noteworthy: if you want truly portable code and in case you have to target weird old linux distributions, cross platform, etc, it might be safer to wait a few years until the bleeding edge stuff has arrived in all necessary compilers etc.

bottomline: I'm slowly getting to know more cpp14 step by step and one thing at a time.
i don't have any problem with any of that because it's all optional. i can just as well keep using my proven to work design patterns and coding styles.
also looking forward to cpp20, appears to be the next big leap in some regards.
added on the 2020-05-05 23:24:55 by jco jco
i don't use new c++ constructs for the heck of it, usually they come to me organically in that someone tells me or i read up whilst researching better ways to jot down what needs doing etc

i'm conservative like that - it also often results in rather easy to read code that doesn't require you to be up to the latest standard to understand
added on the 2020-05-06 10:10:08 by superplek superplek
C++14 is a currently a sweet spot regarding to compiler support. C++17 adds some good stuff imo, especially constexpr std:::string aka string_view, std::filesystem, but full compiler support is not there yet. C++20 adds aggregate initialization (which C99 has had for decades), range-based for with an initializer (hello python).

I extensively use constexpr, auto, range-based for, unique_ptr, std::function and lambdas. I also find find type traits very handy for templates (enable_if and the like). Using override and final can save you some headaches with classes. decltype can be practical for loop variables etc. I also use std::filesystem when I need it.

In my opinion variadic templates are fucked up. I rarely touch those. Prepare for a clusterfuck of unreadable template errors.

I have to disagree with fgenesis about the container stuff. They can make code extremely compact and fast to implement in combination with all the algorithm stuff. Nothing holds you back from plugging in you custom lambda / sort function / whatever. This can solve like 90% of your use-cases.
But yeah. It might not be practical when new is not available. You could write your own allocator, but who wants to do that? ;)

I takes some time to get used to all the stuff, but then you can read it at 3am in a dark room, maybe even drunk. Best drunk probably... ;)
added on the 2020-05-06 11:23:34 by raer raer
Rust looks good btw...
added on the 2020-05-06 11:24:05 by raer raer
Oh yeah, I should point out: I mostly stick to writing plain C99-ish code when I can and it feels right; it's usually more straight-forward and portable. But for stuff like demos, plain C can get a bit tedious, so I often use some more C++-ism there. My "future dream engine" is data-driven with a plain C replayer, and building content using Python-based tooling. But you know, sometimes perfect is the enemy of good, and in the mean time I use C++ as a middle ground... and try to not go insane ;)

A bit off-topic, but not enough to stop me from posting!
added on the 2020-05-06 11:29:38 by kusma kusma
i think C++17 compliant code is the least of your concerns when you're drunk in a dark room at 3AM
extended literal support, inline variables, static_assert, constexpr if and all those template improvements (finally they like sane code instead of a cryptic functional mess) are pretty nice.
The extensions to the stl are a mixed pile of arbitrary ugliness, as usual :P
added on the 2020-05-06 21:51:51 by T$ T$
Smash: I totally see your point, modern C++ makes me want to be a singer, a painter or a dancer, but definitely not a programmer.
added on the 2020-05-06 22:27:54 by keops keops
Mostly C/basic C++ 98/03 without too much of the fancy stuff for me to keep everything readable and not too over-engineered. There are some C++11 features i like, but i'm mostly keepin' it oldskool.
added on the 2020-05-06 22:40:00 by superogue superogue
The difference between C++11 and C++17 honestly isn't that big (which perhaps signifies that if C++17 is important to you, you are using too many obscure language features ;-) ). C++20 looks to be a much more significant update, along the lines of what C++11 was.
added on the 2020-05-07 09:57:35 by Sesse Sesse
Quote:
i have rules. i need to be able to understand the code at 3am, in a dark room, maybe drunk. in such a context i might not remember language constructs that havent already been embedded within me for 20 years. therefore all these silly modern things are right out.


Same here. Actually I'm actively reducing the usage of C++ in general and going back to plain C stuff for a lot of parts of my codebase. Plain C when done right still has some inherent beauty and simplicity, which in my view C++ never matched.

Encapsulation/information hiding in C is perfect (opaque pointers, static functions) - in C++ it's actually a joke (private members exposed in header files yay). There's PIMPL - but that's an additional indirection (and actually nothing else than C opaque pointers).

Having like 5-10 different smart pointer types in C++1x is pure madness to me. I see people slapping them all over the place in their ugly::namespace::ridden codebase.

You need to THINK about memory ownership - to do a good software design you need to have a clear idea about who owns what. Just making everything a smart pointer is not a solution.
added on the 2020-05-07 15:36:49 by arm1n arm1n
(so yeah +1 to kusma)
added on the 2020-05-07 15:38:18 by arm1n arm1n
fwiw my NDS codebase is actually evolving towards a static C replayer and Python tooling.

And using malloc or things that use it (such as new, or snprintf) is a bad idea (especially on the ARM7, because it has 96k RAM total), so I also have to second fgenesis' point about new being assumed available being annoying. But for PC demos this isn't much of a problem.

(Also, I've never started using C++ for my own projects, so I don't really know what to say about it, but if I'd have to choose between C, C++, Rust or Zig for a new project, I'd probably pick either C (because I'm familiar with it) or Zig (because it can interop as easily as C++ with existing C code, and doesn't try to do a galaxy brain).)
added on the 2020-05-07 16:48:04 by porocyon porocyon
New features can be added to C++, but it must be able to compile existing C++ code.
C++ has long history and it is complicated language.
Adding new feature without breaking change seems like rocket science.

I think learning new statically typed compiled systems programming language is better than catching up modern C++.
For example, Nim, Rust, Crystal, Zig, V, etc.

So I use Nim because that can create as fast as C executable because it generate C or C++ code from Nim.
It can also use C and C++ libraries, easier to read code and rich meta programming features.
added on the 2020-05-07 17:27:18 by tomohiro tomohiro
I have found exactly zero use for any of these features (including "auto"); maybe except std::thread.
added on the 2020-05-08 08:20:08 by Navis Navis
Maybe it would be a great idea for people who use these new features to give some example use (probably in the context of demos/graphics) so that we may learn something that I'm sure many of us are missing.
added on the 2020-05-08 09:23:30 by Navis Navis
Well, let's take a random one: Did you ever forget to unlock a mutex in an error path? std::lock_guard helps:

Code:// Assume somewhere there is: mutex mu; bool Foo::bar() { lock_guard<mutex> lock(mu); if (some_func()) { // Error! Give up. return false; } // do something here return true; }


Even in the error path, where it's easy to forget dropping the mutex, it will get auto-released for you.
added on the 2020-05-08 10:36:15 by Sesse Sesse

login