Home > Software design >  Struct with array that changes dynamically
Struct with array that changes dynamically

Time:11-02

I have been looking to change dynamically the values of an array in a struct depending on other variables of the struct.

Let's say I have:

struct foo
{   
    int value1 = 0;
    int value2 = 0;
    int arr[2] = {value1, value2};
};

In the main if I have create an instance fooInstance and I want to associate a value to value1 fooInstance.value1 = 10, how can I update the value in the array ?

Thank you for your time.

CodePudding user response:

If at all possible, use encapsulation. That's the preferred way to create an interface/implementation skew:

struct foo
{   
  int& value1() { return arr_[0]; }
  int& value2() { return arr_[1]; }
  int* arr() { return arr_; }

private:
  int arr_[2] = {0, 0};
};

void bar(foo& v) {
  // access a single value
  v.value1() = 3;

  // access the whole array
  v.arr()[0] = 5;
}

CodePudding user response:

Firstly, if you need an array, then I recommend storing the objects in the array directly.

I question the value (i.e. usefulness) of these aliases such as value1 when the name has no more meaning than referring to arr[i] directly. But I can see the value in case there is a descriptive name available. I'll use a more meaningful example of 2D vector with x, y dimensions. It should be easy to change float to int and change the names to match your attempt.

While Frank's solution using functions is great in most regards, it has a small caveat of having a less convenient syntax compared to variables. It's possible to achieve the variable syntax using operator overloading and anonymous unions. The trade-off is the increased boilerplate in the class definition. Example:

union Vector2 {
    struct {
        float a[2];
        auto& operator=(float f) { a[0] = f; return *this; }
        operator       float&() &        { return  a[0]; }
        operator const float&() const &  { return  a[0]; }
        operator       float () &&       { return  a[0]; }
        float* operator&()               { return &a[0]; }
    } x;
    
    struct {
        float a[2];
        auto& operator=(float f) { a[1] = f; return *this; }
        operator       float&() &        { return  a[1]; }
        operator const float&() const &  { return  a[1]; }
        operator       float () &&       { return  a[1]; }
        float* operator&()               { return &a[1]; }
    } y;

    struct {
        float a[2];
        auto& operator=(float f) { a[0] = a[1] = f; return *this; }
        float* begin() { return std::begin(a); }
        float* end()   { return std::end(a);   }
    } xy;
};

int main() {
    Vector2 v2;
    v2.xy = 1337;          // assign many elements by name
    v2.x = 42;             // assign one element by name
    std::cout << v2.x;     // read one element by name
    for(float f : v2.xy) { // iterate the entire array
        std::cout << f;
    }
}

Note to those unfamiliar with rules of unions: Reading from inactive union member is allowed only through common initial sequence of standard layout structs. This code is well defined, but the reader should be careful to not over generalise and assume that type punning through unions would be allowed; It isn't.

I adapted code from my earlier answer to another question.


It is different parameters coming from different hardwares.

This sounds like generating the accessors shown above with meta programming could be a good approach.

But, if you would like to avoid the complexity, then a more traditional approach would be to just use the array, and use enum to name the indices:

struct foo
{   
    int arr[100];

    enum indices {
        name1,
        name2,
        // ...
        name100,
        name_count,
    };
};

int main()
{
    foo f;
    f.arr[foo.name1] = 42;
}

CodePudding user response:

If you need access through both the individual member variables and through an array member variable, do not copy the data; rather, use the array as "the source of truth", and provide access through the individual variables or the individual member functions.

Here is your example rewritten to "alias" array variables to scalar member variables:

struct foo
{
    foo() : value1(arr[0]), value2(arr[1]) {}

    std::array<int,2> arr;
    int& value1;
    int& value2;
};

Note: this is not a good way of doing anything in production code, just an illustration of how the language lets you do something like this. Normally I would add accessor member-functions instead of member-variable references, because it avoids many problems referenced in the comments, such as breaking the value semantics.

  • Related