Typically a microcontroller pin can be identified with with port number and pin number.Both are compile time constants. A pin can have multiple functions,if used in a big project multiple source file can initialize same pin and break functionality implemented in other module.
I want to implement a compile time list which is initially empty and each time a pin is initialized it will check if the pin is already present in that list, if its present it will give a static assert otherwise it will insert pin information in the list. List is not required at run time.
I don't have enough knowledge of meta programming, It would be great if someone can provide me direction to implement it.If there is already some library for this kind of purpose, please provide the links
What you want is not possible. C++ metaprogramming does not have a state, it's more akin to a functional language than declarative one. So you cannot have a mutable list. The only state can be introduced by creating new types, but there's no available syntax to check if a particular non-nested name is declared or defined.
Multiple source files (compilation units) are compiled independently so there's certainly no "global state" and that makes it more impossible.
Also, note that what you are doing is inherently run-time. The compiler has no tools to check if you are calling the initialization function twice. These calls might be hidden behind some run-time if-else
decisions. And simply writing HAL_GPIO_Init();
no matter how many times in the whole program is not an error.
The simplest thing I can think of is creating a C++ singleton class that is responsible for communicating with pins. You can have a dedicated int init_GPIO
method using error_codes or exceptions if they are enabled. Instead of static_assert
you will have to rely on tests - that singleton works correctly and the return value of init_GPIO
is not ignored.
If you really do not want to bother with singleton, this function template works too:
template<std::size_t GPIO, std::size_t port> int GPIO_init(GPIO_InitStruct& s){
static bool initialized=false;
if(initialized) return <already_called>;
initialized=true;
//Assuming that you want to propagate the return value.
return HAL_GPIO_Init(GPIO, port, s);// Replace with the correct call.
}
If you require thread-safe initialization then use:
template<std::size_t GPIO, std::size_t port> int GPIO_init(GPIO_InitStruct& s){
static std::once_flag initialized;
int ret_val = <already_called>;
auto call = [&](){ret_val = HAL_GPIO_Init(GPIO, port, s)};
std::call_once(initialized, call);
return ret_val;
}
I am not doing anything which require runtime information about pins. For e.g I initialized a pin for sdram and someone else initialized SPI (using common pins). At run time we will send data or receive data. We won't directly work on PIN. Initialization will happen only once for every Pin (with constants PORT, PIN). Second thing is atomic initialization is too expensive and flash heavy for micro controllers.
@sanjay Well take one source file that tries to initialize a pin by any C++ construct. How can the compiler determine whether the program already initialized it? It can't because it does not have access to other source files. That information could theoretically be available at link-time but not sooner.
yes it is difficult across translations. But the problem is real, by looking at the all the files I can see duplicate initialization. There should be some way to handle it either through compiler or through build system.
@sanjay In what way is atomic init too expensive for you? Plus, if you see multiple initializations in different files, you apparently have code duplication. This is a violation of the DRY principle! You should collect all the code into a single function. This avoids errors, and as a byproduct, gives you the central pin register you want,
@jan.sende using atomic increase flash size. some micro controller only have 8KB flash memory or even less than that.