I'm working on some C code for an embedded platform (an ATTiny, though I don't think that matters) which miraculously can compile with C 17. I am trying to use constexpr
and template arguments anywhere possible in order to avoid managing array sizes manually while also avoiding memory allocations, while also producing something that's difficult to express an invalid state with in case coworkers need to modify it later.
I have a particular data type which needs to be able to store a variable number of values (varies in the source code, not during runtime) which would be iterated through at runtime. Using the template array constexpr
size trick and a template argument, I can define an array with an initializer list and then pass it to the object being constructed with a template size argument as follows:
template <typename T, size_t n>
constexpr size_t array_size(const T (&)[n]) { return n; }
uint16_t data_fast_flash[] = { 100, 400 };
LedPattern<array_size(data_fast_flash)> fast_flash{data_fast_flash};
uint16_t data_slow_flash[] = { 100, 900 };
LedPattern<array_size(data_slow_flash)> slow_flash{data_slow_flash};
uint16_t data_double_flash[] = { 100, 200, 100, 1000 };
LedPattern<array_size(data_double_flash)> double_flash{data_double_flash};
The LedPattern
class is as follows, though I'm not sure it's relevant:
template <size_t N>
class LedPattern : public ILedPattern {
public:
explicit LedPattern(uint16_t* delay) : delay_(delay), count_(N) {}
void reset() override { index_ = 0; };
[[nodiscard]] uint16_t current() const override { return *(delay_ index_); }
void next() override {
if ( index_ >= count_) index_ = 0;
};
private:
uint16_t* delay_;
size_t count_;
size_t index_{};
};
All of the above works as expected, but I was hoping to compress the definitions to something where the creation and naming of the uint16_t
arrays could go away, given that I think they're now the most typo-prone part of the setup.
I did find some seemingly related solutions, but they all rely on the standard library, which I don't have on this platform, such as one using std::index_sequence
, and another using std::forward
.
With C 17 but without the standard library, is there some way of constructing something functionally similar with templates that would:
- Only require some sort of initializer list of values
- Could preferably deduce the number of elements in the value list itself
- Would not require any allocations at runtime
Something roughly like the following, though I'm not attached to this exact syntax and I have no problem changing how LedPattern
works internally:
LedPattern fast_flash{100, 400};
LedPattern slow_flash{100, 900};
LedPattern double_flash{100, 200, 100, 1000};
CodePudding user response:
This seems like a job for user-defined-deduction guides. You could do something like the following:
#include <concepts>
#include <cstddef>
#include <cstdint>
using std::size_t;
using std::uint16_t;
template<size_t N>
class LedPattern {
public:
template<typename ...D>
explicit LedPattern(D...d) : delay_{uint16_t(d)...}, count_(N) {}
private:
uint16_t delay_[N];
size_t count_;
size_t index_{};
};
template<std::convertible_to<uint16_t> ...D>
LedPattern(D...) -> LedPattern<sizeof...(D)>;
LedPattern pat{100, 200, 300};
Just replace std::convertible_to<uint16_t>
with typename
if your compiler doesn't support concepts yet.