1

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.

4
  • @AllanWind I don't think C++ is compatible with mapping two objects to the same address to achieve aliasing. Commented Nov 1, 2021 at 18:32
  • You can implement this using a static member pointer array, though the usage is slightly trickier than directly access a data member. Commented Nov 1, 2021 at 18:36
  • I would use the array as the backing storage and employ an accessor pattern to access the members. e.g.: int& value1() { return arr[0] }; Commented Nov 1, 2021 at 18:37
  • @AllanWind I am willing to be able the change the whole array or just change a single value of the structure. I didn't see any other way to do it except having both Commented Nov 1, 2021 at 18:40

3 Answers 3

3

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;
}
Sign up to request clarification or add additional context in comments.

11 Comments

Thank you, this is a really nice answer it gives me the features I am looking for but I need to do that for more than a 100 variables. It will definitely complexify my code.
It's worth pointing out that this union-based approach is squeaking by not being UB on a technicality (common initial sequence), and should not be used as a general template.
I think an answer like this one needs the rules for common initial sequence to be very prominently provided.
@FrançoisAndrieux & Frank. I added an addendum.
@yacth More than 100 variables in a class smells like potentially bad design. If you intend to name all of them "value1, value2, ...", then pay attention to my questioning of the "value" (i.e. usefulness) of those variables, and consider just using an array directly and get rid of all of the other variables.
|
3

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;
}

7 Comments

@yacth No issue with C++11. If you get rid of the default member initialization (which you have in your own code), this will work all the way back to C++98
Ok thank you for your answer
Since I am new to C++, maybe there is something that I don't understant. But let's say I define the struct without the array, is it possible to access each value using a pointer with a counter since I am using only int and store them in an array later ?
@yacth I'm not sure what you mean exactly. If you mean "define the struct without initializing the array" i.e. struct foo{ ... int arr_[2]; } then sure, no problem. The array would still be defined, it just would not have a set value upon construction.
@yacth No, pointer arithmetic requires the elements to be in the same array. There is no pointer operation you can do to get a pointer to f.value2 from a pointer to f.value1.
|
1

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.

2 Comments

This breaks value semantics. Copy construction and copy assignment are broken. You'd need to provide your own copy constructor. Assignment also becomes very difficult to implement correctly and requires placement new over this which is dubious to say the least. Reference data members (like const data members) should be avoided in almost every case.
Thank you for you answer, actually I am using C++ for some features, but mostly I must stuck to C formatting. Thus I don't know if I am allowed to do so. But yes I want to be able so access the values separatly and to access the whole array aswell.

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.