3

Just recently started C++ programming for micro-controllers, and I've ran into situations* where it would be convenient to have a non-static const field on a struct that is always guaranteed to have a fixed value (same for every instance of the sturct, ever).

Given a struct

struct S {
    const uint8_t c; // Should always be 42
    char v;
    uint32_t arr[4];
}

I'd like c to be a constant value, and the same constant value every time. I would love to be able to use the convenience of brace initializer lists, for setting v and the members of arr like

S some_var = {'v', { 0, 1, 2, 3 } };

Since I'd like c to be a constant, I'm under the impression that I have to use an initializer list for setting c, such as S() : c(42) {}, which works just fine, as long as I don't try to also initialize arr, in which case I'm lost on how the list should look like. Is this doable using C++11? (Also interested in an answer if this is not doable in C++11, but in some newer standard.)

Example code:

#include <stdio.h>
#include <stdint.h>

struct S {
    const uint8_t c; // Should always be 42 on every instance
                     // of the struct due to hardware shenanigance
                     // (i.e. this struct is the representation of a register value)
    char v;
    uint32_t arr[4];

    // This allows using "S s1;"
    S() : c(42), v('a'), arr{} {}

    // This allows using "S s2 = { 'v', 0, 1, 2, 3 };" works but it's clumsy:
    S(uint32_t v, uint32_t arr0, uint32_t arr1, uint32_t arr2, uint32_t arr3) :
        c(42), v(v), arr{ arr0, arr1, arr2, arr3 } {}

    // I would like to do something along the lines of "S s2 = { 'v', { 0, 1, 2, 3 } };":
    // S(uint32_t v, uint32_t arr[4] /*?*/) :
    //     c(42), v(v), arr{/*?*/} {}

};

// Main just for the sake of completeness
int main() {
    // Works just fine
    S s1;
    printf("s1.c = %u\n", s1.c); // 42
    printf("s1.v = '%c'\n", s1.v); // a
    printf("s1.arr[3] = %u\n", s1.arr[3]); // 0

    // Initialiation like this works with the line:12 signature:
    S s2 = { 'v', 0, 1, 2, 3 };

    // I'd like to initialize like this:
    // S s2 = { 'v', { 0, 1, 2, 3 } };

    printf("s2.c = %u\n", s2.c); // 42
    printf("s2.v = '%c'\n", s2.v); // v
    printf("s2.arr[3] = %u\n", s2.arr[3]); // 3
    return 0;
}

*Context on why I'd want to do this: This might seem like a weird thing to want, since if the value is always the same, why bother storing it? Well imagine that the struct in question is a bitfield which corresponds to the register of an IC with which the micro-controller communicates. These registers sometimes have "reserved" fields, and the datasheet specifies what value you must write into these fields. From a programmer's point of view, it would be convenient if I never had to deal with setting said bits manually.

4
  • If all instances will have the same value, sounds like you need a static variables. Not only will that save space since all objects will share a single variable, it also allows you to keep your aggregate initialization. Commented Oct 18, 2019 at 13:40
  • 1
    @NathanOliver Please read the footnote at the end of the question. Imagine you want to pass a bitfield to some hardware of the form 1111000xxxxxxxxx where the 11110000 is always the same/required and the xxxxxxxx is the data you want to communicate. Commented Oct 18, 2019 at 13:41
  • Note: It's likely that the compiler inserts padding between v and arr, which will probably mess up the communication with the microcontroller. Most compilers allow you to avoid the padding (by something like #pragma pack) but that's not really portable. Commented Oct 18, 2019 at 13:44
  • Yes, @MaxLanghof is correct, I need the value to be physically present in memory. As far as I understand a static const does not satisfy this. Any tips on how to reword the question so that this is more clear? I'm aware of the padding issue, I just wanted to keep the example simple. Commented Oct 18, 2019 at 13:45

1 Answer 1

6

C++11 gives you std::array which is like a raw array, but comes with none of the "negatives" (array decay, can't copy). Using that you can get exactly what you want like

struct S {
    const uint8_t c = 42;
    char v = 'a';
    std::array<uint32_t, 4> arr{};

    // This allows using "S s1;"
    S() {}

    S(uint32_t v, std::array<uint32_t, 4> arr) : v(v), arr{arr} {}
};

// Main just for the sake of completeness
int main() {
    // Works just fine
    S s1;
    printf("s1.c = %u\n", s1.c); // 42
    printf("s1.v = '%c'\n", s1.v); // a
    printf("s1.arr[3] = %u\n", s1.arr[3]); // 0

    S s2 = { 'v', { 0, 1, 2, 3 } };

    printf("s2.c = %u\n", s2.c); // 42
    printf("s2.v = '%c'\n", s2.v); // v
    printf("s2.arr[3] = %u\n", s2.arr[3]); // 3
    return 0;
}

which outputs

s1.c = 42
s1.v = 'a'
s1.arr[3] = 0
s2.c = 42
s2.v = 'v'
s2.arr[3] = 3

If you absoluytley have to have a raw array in S then your other option is to use a std::initializer_list in the constructor. That would look like

struct S {
    const uint8_t c = 42;
    char v = 'a';
    uint32_t arr[4]{};

    // This allows using "S s1;"
    S() {}

    S(uint32_t v, std::initializer_list<uint32_t> data) : v(v)
    {
        int i = 0;
        for (auto e : data)
            arr[i++] = e;
    }
};

// Main just for the sake of completeness
int main() {
    // Works just fine
    S s1;
    printf("s1.c = %u\n", s1.c); // 42
    printf("s1.v = '%c'\n", s1.v); // a
    printf("s1.arr[3] = %u\n", s1.arr[3]); // 0

    S s2 = { 'v', { 0, 1, 2, 3 } };

    printf("s2.c = %u\n", s2.c); // 42
    printf("s2.v = '%c'\n", s2.v); // v
    printf("s2.arr[3] = %u\n", s2.arr[3]); // 3
    return 0;
}

And you get the same results as the code using std::array.

Sign up to request clarification or add additional context in comments.

3 Comments

It's not often that you see uniform initialization pulling through like this :D
@MaxLanghof I'd say it works about 75% of the time for me. Really the main issue is legacy containers. When you start from fresh you can build a better overload set for your constructors.
In my head I was looking for the std::initializer_list solution, but the std::array one seems so obvious, I feel bad for not even trying it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.