Search code examples
c++stm32low-levelbare-metal

Is it possible to declare an anonymous instance in an union in C++?


While working with low-level C++ for bare-metal embedded systems (STM32), I came across a use case for an "anonymous union member" declaration with non trivial (a struct) type. Is such a thing possible ?

I have tried the following kind of code :

struct Specialization_CR1_t
{
        uint32_t a :1 ;
        uint32_t   :31;
};

struct CR1_t
{
    union
    {
        struct
        {
            uint32_t  : 1;
            uint32_t b: 1;
            uint32_t  :30;
        } ;
        // Few tries...
        // Specialization_CR1_t {};
        // Specialization_CR1_t;
        Specialization_CR1_t () ; 
        // Fails with "expected unqualified-id before ')'"
    };
};

int main()
{
    //The goal :
    struct CR1_t CR1;
    CR1.a = 1;
    CR1.b = 0;
}

The goal is to avoid having CR1.<thing>.a. Obviously, there is the possibility to directly declare Specialization_CR1_t anonymously in the union, however the ultimate goal is to be able to use a template and tmpl_CR1_t instead of "just" Specialization_CR1_t.

I am aware of the potential memory issues with the stuff displayed here. However, since this is in bare-metal embedded environment, the memory structure is fully known, the bits are packed well and tight and the tool-chain is fixed (no issues with bitfields).

Moreover, since this structure will be directly mapped in memory, I cannot afford the overhead of another variable. The total size of my structure have to be 32 bits and writing into a or b have to change only the correct bit.

Best regards !


Solution

  • ISO C11 allows anonymous structs inside other struct/unions. This is supported as an extension by some C++ compilers, including GNU-compatible (g++, clang++), and by MSVC++. The GCC manual has some examples.

    This is AFAIK not allowed in ISO C++. If you're using a compiler that doesn't implement this extension, see the middle part of this answer.


    I'm pretty sure the anonymous union is a red herring here, and you'd have exactly the same problem trying to declare b as a 32-bit object with 31 bits of padding and 1 value-bit using that anonymous-struct in any other context.

    If it was legal inside an anonymous union, it would be legal anywhere. (But instead it's not, and is legal nowhere in ISO C. As @Peter points out in comments, the name Instance in struct {<members>} Instance can't be omitted. And if you make it struct foo {<members>}; you'd just be declaring a type, not an instance.)

    You'll probably just have to write a class with operator= and operator bool overloads, which you can do because this is C++.

    (Consider retitling the question with what you're actually trying to do: write an anonymous struct with a bitfield member.)

    Or if you want help with the wrapper class, take a step back from the X-Y problem where the anonymous-struct approach has apparently hit a dead end (unless there is some compiler-specific support for it) and ask a new question about writing a convenient wrapper that exposes a single bit in a word as an integer type. With vendor headers, compiler extensions, or plain or C++ operator overloads.

    Although given how you're using the union, you can do this to get what you want, at least without templates, right?

    struct CR1bits
    {
        uint32_t a: 1;
        uint32_t b: 1;
        uint32_t  :30;
    };
    

    So maybe you should be asking something about how you can template this to what, have different type names that have some subset of the named bits? Maybe the C preprocessor can help with that, in a more clunky way that still lets the code using these types look the way you want.


    Anonymous structs are C11 standard, or a C++ extension:

    GNU C++, and MSVC, both support anonymous structs. This compiles and works:

    union Obj {
       struct {       // extension: anonymous struct
          int x;
          int y;
          int z;
       };
       int elems[3];
    };
    

    a->x or a->elems[0] both access the same object (assuming standard struct layout with no padding).

    And apparently this is standard ISO C11.