Move through the fields of a C / C ++ record without using its name

0

Is it possible to move through the fields of a C / C ++ registry without knowing its name or the size of these in memory? that is, suppose we have:

struct Ejemplo{
    int a;
    int b;
    int c;
    int d;
    ...
    int z;

};
struct Ejemplo var;

Right now if I want to assign a value to each field I must do it field by field in the form:

var.a = 1;
var.b = 2;
...
var.z = N;

I'm interested in doing something like this:

for(int i=0; i<N; i++){
    var.algo = i;
}

Is this possible? Is not it worth the option to assign a pointer to var and go scrolling from 4 to 4 bytes? Since in the real example not all the fields are int some may occupy more or less bytes.

    
asked by Enri Duran 21.11.2016 в 13:08
source

2 answers

2

You are answering yourself. Without using any device, what you ask is not possible.

On the one hand you have to think that the data can be aligned or not. Alignment is a feature that makes each field in the structure at the beginning of a memory record.

A merely illustrative example. Assuming the following structure:

struct test
{
  unsigned a : 3; // campo de 3 bits
  unsigned b : 6; // campo de 5 bits
  unsigned c : 1; // campo de 1 bit
};

Its structure in memory could look like this:

             | 8 bits | 8 bits | 8 bits | 8 bits |
Alineado:     aaa      bbbbbb   c
No alineado:  aaabbbbb bc

As you can see, if the records are not aligned, it may be the case that a field involves several records, which complicates the reading and writing operations.

That the data may or may not be aligned complicates the process of jumping from one record to the next, since it may not be obvious to know the increase to apply in each case.

On the other hand, you yourself comment that each field can be of a different type. To perform the jumps you should take into account that circumstance, otherwise you will jump to an incorrect memory position.

Now, through certain artifices these problems can be solved. The downside is that you'll have to keep more code. For example you can use BOOST_FUSION_ADAPT_STRUCT if you're working with C ++ :

struct Ejemplo{
    int a;
    float b;
    double c;
    std::string d;
};

// Mapeamos la estructura
BOOST_FUSION_ADAPT_STRUCT(
    Ejemplo,
    (int, a)
    (float, b)
    (double, c)
    (std::string, d)
)

// Función que será llamada para cada miembro
template<class T>
void ImprimirValor(T& param)
{
  std::cout << param << '\n';
}

int main()
{
  Ejemplo ejemplo = { 1, 2.0, 3.0, "test" };
  boost::fusion::for_each(ejemplo,ImprimirValor);
}
    
answered by 21.11.2016 в 13:29
2

Edited

In the comments:

  

I work with a function that returns the value of a field and every time it is invoked it returns the value of the next field in that record

As I understand it, the function you speak of works sequentially, in which case you can settle a solution (which is not simple either). We will start by saving the elements that we want to assign in the structure itself:

struct Ejemplo{
    char c;
    short s;
    int i;
    float f;
    double d;

    // Coleccion de elementos asignables de la estructura
    static constexpr auto data = std::make_tuple(
        &Ejemplo::c,
        &Ejemplo::s,
        &Ejemplo::i,
        &Ejemplo::f,
        &Ejemplo::d
    );
};

Then we use a recursive function that goes element to element of the collection and calls a function to assign them value:

template <std::size_t ELEMENTOS>
void asigna(Ejemplo &ejemplo)
{
    constexpr auto data_size = std::tuple_size<decltype(Ejemplo::data)>::value;
    static_assert(ELEMENTOS <= data_size);
    auto miembro = std::get<data_size - ELEMENTOS>(Ejemplo::data);
    ejemplo.*miembro = funcion_que_me_devuelve_el_valor_de_un_campo();
    asigna<ELEMENTOS - 1>(ejemplo);
}

// Funcion vacia que rompe la recursion
template <>
void asigna<0u>(Ejemplo &) {}

With this approach you can assign the values of Ejemplo sequentially with this call:

Ejemplo var;
asigna<5u>(var);

If you have the possibility to compile in C ++ 17 the solution is slightly more interesting:

template <std::size_t ELEMENTOS, typename FUNCION>
void asigna(Ejemplo &ejemplo, [[maybe_unused]] FUNCION funcion)
{
    if constexpr (ELEMENTOS)
    {
        constexpr auto data_size = std::tuple_size<decltype(Ejemplo::data)>::value;
        static_assert(ELEMENTOS <= data_size);
        auto miembro = std::get<data_size - ELEMENTOS>(Ejemplo::data);
        ejemplo.*miembro = funcion();
        asigna<ELEMENTOS - 1>(ejemplo, funcion);
    }
}

You save the empty function to break the recursion by using if constexpr and we can add the function that returns the value of the next field as a parameter ... which we could not before because the functions templates can not be specialized, with this new approach in C ++ 17 the call would be:

Ejemplo var;
asigna<5u>(var, funcion_que_me_devuelve_el_valor_de_un_campo);

You can see the code [working here] .

Original reply

  

I'm interested in doing something like this:

for(int i=0; i<N; i++){
    var.algo = i;
}

This is possible to do (but it's not practical) using member pointers:

struct Ejemplo{
    int a;
    int b;
    int c;
    int d;
    int z;

    using puntero_a_miembro = int Ejemplo::*;
    constexpr static puntero_a_miembro datos[5]{
        &Ejemplo::a,
        &Ejemplo::b,
        &Ejemplo::c,
        &Ejemplo::d,
        &Ejemplo::z
    };
};

We save in Ejemplo::datos 1 the position in Ejemplo of each of its members and then assign them using a loop:

Ejemplo var;

for (int i = 0; i < 5; ++i){
    var.*Ejemplo::datos[i] = i;
}

Not practical because:

  • The syntax is confusing.
  • Requires manually maintaining the Ejemplo::datos fix: If new data is added to the structure, we need to add them to the array.
  • Not valid with different types within the structure: The pointers of Ejemplo::datos are integers ( int ) belonging to Ejemplo ; if instead of integers were other data another pointer would be needed.

If at the end, all the data are going to be of the same type, why not use an internal array?:

struct Ejemplo{
    int datos[5]{};
} var;

for (int i = 0; i < 5; ++i){
    var.datos[i] = i;
}

1 Being a static member, it does not increase the size of the structure.

    
answered by 21.11.2016 в 15:16