So, I’m not much of a C++ programmer, and I only know basic templates (the typical example of max(x, y) where you template T x, T y so that it works for int, float, whatever.
I already know that the more advanced param templates (whatever they’re called), are a pain in the rear as they make it much harder to deal with those objects in other files/objects since the template variables must be resolved at compile time and can’t be passed as argument / figured out at runtime.
What’s the point of using that syntax for pin and rgborder outside of making those impossible to use as runtime variables (issue I hit in the past)?
Why not just use normal object private variables that get set as object instantiation time? @Daniel_Garcia and @Mark_Kriegsman probably worked on the original file and know his, but if not, @Yves_BAZIN might too?
template <int refreshDepth, int matrixWidth, int matrixHeight, unsigned char panelType, unsigned char optionFlags>
CircularBuffer SmartMatrix3RefreshMultiplexed<refreshDepth, matrixWidth, matrixHeight, panelType, optionFlags>::dmaBuffer;
I’m not even sure I can make sense of that syntax or why it’s there, although it sure makes pixelmatix seemingly impossible to use inside a library since it needs all those templates arguments that the library can’t know at compile time.
More generally, where do I read about the syntax of those templates and why they even exist? Normal C++ templates pass types as argument, not values. http://www.cplusplus.com/doc/oldtutorial/templates/ does briefly explain ‘Non-type parameters for templates’ but not why they exist.
The short explanation is that by using a template, it is possible for the compiler to choose the fastest possible implementation of I/O operations for that particular pin, at compile time with zero runtime cost.
Different specific pins support different hardware-level I/O operations, and using a template lets the fastest possible code be selected without resorting to a chain of runtime if/then/else or switch/case statements to choose of the pin I/O method. Compile-time code path selection results in faster run-time execution speed, and in a microcontroller environment like Arduino, runtime clockcycles are precious, and runtime speed is essential.
So, it’s a trade-off: with the pin number locked in at compile time, there is one things that is more difficult/expensive (runtime reconfiguration of hardware pin assignments), but the generated code is smaller and faster for all the most common cases, where the hardware is not rearranged on the fly.
Thanks @Mark_Kriegsman . I guess I’ve been doing this with #defines so far which I’m not sure is better, but the object definitions have a single signature and are easily re-definable in other
With my first FastLED_NeoMatrix, templates in FastLED forced me to do this terrible thing:
this cured me from ever wanting to use templates :-/
That said, not your code, but do you understand the use of templates here
Is it pretty much the same thing, but with a lot of syntaxic ugliness induced by the same use of templates?
C++ templates can be specialized on a number of different things, and ‘integers’ (e.g. pin numbers, or array sizes) have been supported since the 1990s; they’re not one of the new C++14 or C++11 features or anything like that…
They’re often called “non-type template parameters” (if you’d like to google more about them), and this Stack Overflow page has some helpful discussion and examples. https://stackoverflow.com/questions/499106/what-does-template-unsigned-int-n-mean
That code you linked looks like it has a similar desire: to automatically select the ‘best’ implementation at compile time, and to avoid runtime overhead. But as you noted, it’s not code I’m familiar with and it looks pretty sophisticated, so I haven’t gotten my head all the way around it.
As for your chain of if/then/else in the setup function, what’s going on there is that you’re compiling in optimized versions of the code for each pin number; assuming you have code space for it, that’s an excellent option – if a bit ugly.
The microcontroller world really isn’t oriented toward “dynamically reconfiguring the hardware on the fly”… even though sometimes that would be useful. In those rare cases, we have to either take a runtime speed hit, or duplicate the code, as you did. Again, it’s a trade-off – definitely a hallmark of the microcontroller world.
I think what I learned so far trying to make a library that inherits from/encapsulates FastLED and now SmartMatrix, is that it seems impossible if they use templated arguments, which is a shame.
For FastLED I was thankfully able to use the CRBG array it spits out and pass it as a simple array to my object creator, so it worked out in the end, which is how FastLED::NeoMatrix now works (although it felt suboptimal that the caller had to create the fastled object instead of it being created internally in my library object, but seemed unavoidable due to templates and it wasn’t a huge deal in the end).
For SmartMatrix, I may have to use an uglier workaround due to how deep templates run into that code and how my library cannot know those template arguments when it’s compiled separately from the caller code, and can’t pass a smartmatrix object at all since it’s impossible to define its type in my library object signature which is compiled separately from smartmatrix and linked at the end.
But I suppose on microcontrollers it may make sense to lose that flexibility and gain a few resources on microcontrollers that don’t have many such resources to start with.
Thanks for explaining the tradeoffs and the reason behind the apparent madness
Still, if I even have to write a library with variable implementations, I’ll try to work my way around it with #defines so that I don’t have to have variable function/object signatures that are impossible to reference in a separate library.
That’s probably also why templates are banned at work (inability to C++ inherit code and use in libraries, but it’s on computers with gobs of RAM, not microcontrollers)
@Marc_MERLIN Lots of ways to do this, for sure. I think define/ifdef has the same overall brittleness about compile-time selection which then prevents runtime choices… but as I said, there are a ton of trade-offs in all this, and different choices are right for different projects. Hope you’re able to get where you need to get to!
define/ifdef was the way the library originally did the assembler optimizations for port writing - and they became un-usable the moment I wanted to have output on multiple pins.
The base CLEDController object is not template based, so you can create that/pass it in - but it sounds like you figured that out.
I do continue to prioritize performance (especially on low-clock systems, where the compile-time defined pins/rgb ordering make the asm code for 8/16Mhz avr and 16Mhz ARM M0 platforms even possible) for embedded systems/projects over flexibility for generic controllers/libraries.
Some things will benefit from constexpr (and i’m playing with this a little bit with the RGBW branch/rewrite) - for example I think the rgb ordering stuff might be able to take advantage of this - but I haven’t found a good way to have constexpr’s accessible in asm blocks in a way that allows using the constants as immediates in the assembler code (on both avr and arm I really don’t have any room for more registers, or clock cycles for extra rounds of memory loading/unloading)
Thanks for the history @Daniel_Garcia . Indeed for FastLED it was not a huge deal to get around it. First I tried to inherit from CFastLED and indeed that was a dead end due to templates.
In the end I figured out it was much easier (and better) to let the caller define the led matrix in their main, and pass it to the library object. Given that CRGB is a nice struct, it’s easy to pass as a library object argument, so I was able to make it work.
With SmartMatrix however the entire thing is template ridden and it’s impossible to pass anything outside of the scope of the main file with all the defines as a result.
I ended up going the ugly route which is to define a CRGB framebuffer, use that in my object, and then copy the framebuffer into PixelMatrix pixel by pixel
So indeed templates are better when used in moderation :)h