0

I'm trying to write good readable code for a small embedded project which I think can be reused for (many?) other projects. The idea is to build a uint8_t array with variadic templates on compile time, something like this:

static uint8_t* read1;
I2C::CmdQueue<0x0f,
    I2C::Read<&read1, 3>,
    I2C::Write<7, 7, 7, 7>> u;

In this case 0x0f is an I2C address, I2C::Read reads 3 bytes which can be read by pointer read1, I2C::Write writes 7, 7, 7, 7 to the I2C bus. This can (or should) be archived with the following code:

namespace I2C {
                                      
        template<uint8_t** POS, uint8_t COUNT>
        struct Read {         
                Read() { *POS = data; }
                uint8_t type = 0x80 | COUNT;
                uint8_t data[COUNT] = { COUNT };
        };                          
                      
                                      
        template<uint8_t ... Cs>                                   
        struct Write {                  
                uint8_t type = 0x7f & sizeof...(Cs);
                uint8_t data[sizeof...(Cs)] = { Cs... };
        };           
 
                               
                                 
        template<typename ...> // primary template, used for termination, 
        struct Cmds {          // no element - but has size 1 in c++, in c size is 0!
                uint8_t make_me_size_zero[0];
        };                             
                                                                              
        template<typename T, typename ... Ts> // al least one element
        struct Cmds<T, Ts ...> {
                T t;                     
                struct Cmds<Ts...> cmd;
        };                                         
                                        
        template<uint8_t** POS, uint8_t COUNT> // specialization
        struct Cmds<struct Read<POS, COUNT>> {
                Read<POS, COUNT> read;
        };
                                 
        template<uint8_t ... Cs> // specialization
        struct Cmds<struct Write<Cs ...>> {                        
                Write<Cs ...> write;
        };      

                              
        template<uint8_t DEVICE, typename ... Ts>
        struct CmdQueue {                             
                uint8_t data[0];
                uint8_t status;
                const uint8_t device = DEVICE;
                struct Cmds<Ts...> cmds;
        };                       
                                                                   
 

        template<uint8_t DEVICE, typename ... Ts>
        union CmdQueueU {                      
                CmdQueueU() {}
                              
                uint8_t data[sizeof(CmdQueue<DEVICE, Ts...>)];
                CmdQueue<DEVICE, Ts...> cmds;
        };                    
}

With this code above I ran into the following problems:

  • depending on the compiler and optimization (e.g. avr-c++ (GCC) 12.1.0) only the space for the array is reserved and no data (I2C address, Read, Write, ...) is written
  • to bypass this problem I added the union CmdQueueU which should - from my point of view - "show" the compiler that the whole data from the template is used. Did not work, even worse, nothing is initialized when accessing and dumping data form CmdQueueU
  • clang++ (13.0.1) is on strike with

non-type template argument refers to object 'read1' that does not have linkage

even when declared static. gcc does not complain. I think it has something to do with clang error: non-type template argument refers to function that does not have linkage -- bug? and/or https://github.com/llvm/llvm-project/issues/17404 Please note: I want to use gcc for my project, clang was used only for testing...

The big question - for me - is, does anybody see a chance to get this code running? Perhaps even without make_me_size_zero[0] to avoid compiler extensions?

tw1st
  • 1
  • 1
  • and use `std::array` – Goswin von Brederlow Jun 06 '22 at 20:26
  • 2
    Actually, I am not sure anymore how this is supposed to be used. Can you provide a more detailed use case and explain what exactly it should do? What exactly should be done at compile-time? I don't understand what the union was intended for at all or what the zero-length array is supposed to do. – user17732522 Jun 06 '22 at 20:34

0 Answers0