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.

yat
  • 35
  • 5
  • @AllanWind I don't think C++ is compatible with mapping two objects to the same address to achieve aliasing. – François Andrieux Nov 01 '21 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. – François Andrieux Nov 01 '21 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] };` – Wyck Nov 01 '21 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 – yat Nov 01 '21 at 18:40

3 Answers3

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;
}
  • @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 –  Nov 01 '21 at 18:46
  • Ok thank you for your answer – yat Nov 01 '21 at 18:47
  • 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 ? – yat Nov 01 '21 at 18:58
  • @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. –  Nov 01 '21 at 19:02
  • @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`. – François Andrieux Nov 01 '21 at 19:05
  • Sorry I mean I just create the structure without the array. But later I extract the value of the structure and store them in an array with a pointer and a counter (because I need an implementation that works for a huge set of data, it will be easier with a counter). – yat Nov 01 '21 at 19:05
  • @FrançoisAndrieux Oh that was my question, thank you for your answer. I am trying to find a better way to implement that. – yat Nov 01 '21 at 19:07
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;
}
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 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. – yat Nov 01 '21 at 19:12
  • 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. –  Nov 01 '21 at 19:12
  • I think an answer like this one needs the rules for common initial sequence to be very prominently provided. – François Andrieux Nov 01 '21 at 19:12
  • 1
    @FrançoisAndrieux & Frank. I added an addendum. – eerorika Nov 01 '21 at 19:16
  • @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. – eerorika Nov 01 '21 at 19:20
  • It is different parameters coming from different hardwares. They will have specific names. That is why I wanted to be able to access each parameter but the whole vector aswell since I need the whole vector for calculation. – yat Nov 01 '21 at 19:24
  • Is the vector used for an internal computation or is it made publicly accessible? My fear is always some well-meaning fool will use an exposed data mirror in such a way that it is knocked out of synch. Part of the reason that you don't want a mirror in the first place. – user4581301 Nov 01 '21 at 19:30
  • @yacth That sounds reasonable to me. See edit. – eerorika Nov 01 '21 at 19:31
  • @user4581301 It is used for an internal computation. But I still need to have access to the independant values to check if they are on a certain range. (Let's say I am really pythonic, and dictionnaries in python have the perfect feature I am looking or) – yat Nov 01 '21 at 19:44
  • *dictionnaries in python have the perfect feature I am looking or* If all of the values share a common type, `std::unordered_map` may be of use, but in general don't try to write C++ code the way you'd write python. That way lies inefficient and oft-confused code that's a pain to work with and maintain. – user4581301 Nov 01 '21 at 19:50
  • Sure that is why I am trying to use structures in here, since they are usually used to store hardware inputs as I saw on some examples. – yat Nov 01 '21 at 19:56
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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 3
    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. – François Andrieux Nov 01 '21 at 18:35
  • 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. – yat Nov 01 '21 at 18:38