Безопасный способ ссылки на вложенный член - PullRequest
0 голосов
/ 05 мая 2020

У меня есть struct с некоторыми другими struct участниками. И внешняя, и внутренняя структуры - StandardLayout (можно даже предположить, что внутренние - это просто старые данные). Примерно так:

struct Inner1 {
    int a = 0, b = 0;
};
struct Inner2 {
    int c = 0, d = 0;
};
struct Outer {
    Inner1 x;
    std::string s;
    Inner2 y;
};

Я хочу написать некоторую функцию, которая принимает Outer& и объект некоторого типа T, который может возвращать значение любого вложенного поля, в зависимости от аргумента:

int get(Outer& o, T field);

Если бы Outer была плоской структурой, указатели на член были бы именно тем, что мне нужно, но это не плоская

Тривиальный способ - сделать T а enum всего поля и напишите switch, но я это не дееспособный. Более быстрый способ - сделать T смещением и написать

int get(Outer& o, size_t field) {
    return *reinterpret_cast<int*>(reinterpret_cast<char*>(o) + field);
}

, а затем назвать его как get(o, offsetof(Outer, y) + offsetof(Inner2, c)). Это работает, но я не уверен, что это сработает гарантированно - правильно ли суммировать подобные смещения и безопасно ли брать значение члена по смещению.

Итак, вопрос: вот так сейф? Если нет, есть ли безопасный способ? Создание значений T может быть произвольным сложным, однако их использование должно быть быстрым.

Мотивация: мне нужно будет поместить значения из некоторых вложенных полей в некотором порядке, известном при запуске, но не во время компиляции . Я хотел создать массив T при запуске, а затем, при получении определенного объекта, использовать этот предварительно вычисленный массив.

[UPD]: Итак, он будет использоваться следующим образом:

void bar(int);
void foo(Outer& o, vector<T>& fields) {
    for (auto& field : fields) {
        bar(get(o, field));
    }
}

Ответы [ 3 ]

1 голос
/ 05 мая 2020

Вы можете сделать это так.

/* main.cpp */

#include <string>
#include <iostream>

using namespace std;

struct Inner1 {
    int a = 0, b = 0;
};

struct Inner2 {
    int c = 0, d = 0;
};

struct Outer {
    Inner1 x;
    std::string s;
    Inner2 y;
};

struct OuterMember
 {
  int (*getter)(Outer &obj);
 };

inline int get(Outer &obj,OuterMember field) { return field.getter(obj); }

template <auto Ptr1,auto Ptr2>
auto GetInnerMember(Outer &obj) { return (obj.*Ptr1).*Ptr2; }

inline constexpr OuterMember OuterMemberA = { GetInnerMember<&Outer::x,&Inner1::a> } ; 

inline constexpr OuterMember OuterMemberB = { GetInnerMember<&Outer::x,&Inner1::b> } ; 

inline constexpr OuterMember OuterMemberC = { GetInnerMember<&Outer::y,&Inner2::c> } ; 

inline constexpr OuterMember OuterMemberD = { GetInnerMember<&Outer::y,&Inner2::d> } ; 

/* main() */

int main()
 {
  Outer obj;

  obj.x.a=1;
  obj.x.b=2;
  obj.y.c=3;
  obj.y.d=4;

  cout << "a = " << get(obj,OuterMemberA) << endl ;
  cout << "b = " << get(obj,OuterMemberB) << endl ;
  cout << "c = " << get(obj,OuterMemberC) << endl ;
  cout << "d = " << get(obj,OuterMemberD) << endl ;

  return 0;
 }
1 голос
/ 05 мая 2020

Я считаю, что это безопасно (не нарушая строгого алиасинга).

Однако у языка есть лучший механизм для этого: указатели на элементы данных, которые компилируются с точностью до смещения.

Предостережение в том, что вам придется делать отдельные перегрузки для Inner1 и Inner2

int get(Outer& o, int Inner1::* m) {
    return o.x.*m;
}

int get(Outer& o, int Inner2::* m) {
    return o.y.*m;
}

int foo() {
  Outer tmp;
  return get(tmp, &Inner1::a) + get(tmp, &Inner2::d);
}

0 голосов
/ 05 мая 2020

Вы можете добиться того же, используя специализацию шаблона функции, см. Пример кода ниже

#include <iostream>

using namespace std;

struct Inner1 {
int a = 1, b = 2;
};
struct Inner2 {
int c = 3, d = 4;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};

template<typename T>
int get(Outer&o);

template<>
int get<Inner1>(Outer& o)
{
 return o.x.a;
}

template<>
int get<Inner2>(Outer& o)
{
  return o.y.c;
}

int main()
{
  Outer out;
  std::cout << get<Inner1>(out)  << std::endl;
  std::cout << get<Inner2>(out)  << std::endl;

  return 0;
}

Надеюсь, это поможет !. Более интересно то, что это типобезопасный.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...