#c #c 17 #visitor-pattern #std-variant
Вопрос:
Существует старый устаревший код, который невозможно изменить. Если упростить, то это выглядит так:
enum class Type {A, B, C};
struct Object {Type type;};
Object* objs[N];
int count = 0;
#define addobj(ptr) objs[count ] = (Object*)ptr
struct A {
Type type;
int prop1;
A(int v) : type(Type::A), prop1(v) {addobj(this);}
};
struct B {
Type type;
int prop1;
B(int v) : type(Type::B), prop1(v) {addobj(this);}
};
struct C {
Type type;
int prop1;
C(int v) : type(Type::C), prop1(v) {addobj(this);}
};
A* getA(int id) {return (A*)objs[id];}
B* getB(int id) {return (B*)objs[id];}
C* getC(int id) {return (C*)objs[id];}
Кроме того, существует «полиморфный» доступ к свойствам, который разрешается изменять:
intamp; prop1ref(int id) {
switch (objs[id]->type) {
case Type::A: return getA(id)->prop1;
case Type::B: return getB(id)->prop1;
}
return getC(id)->prop1;
}
void test() {
A a(1); B b(2); C c(3);
for (int id=0; id<count; id )
prop1ref(id);
}
Есть ли способ заменить только код доступа к свойствам в prop1ref, например, std::variant и std::visit?
Пожалуйста, обратите внимание, что имя и тип prop1 действительно совпадают между классами, но местоположения (смещения) не совпадают. Смещение поля типа гарантировано, поэтому всегда можно выполнить приведение. Кроме того, новый код должен позволять получать доступ к двойным реквизитам 2, строковым реквизитам 3 и т.д. В классах A, B, C без использования макросов.
Ответ №1:
Возможно, что-то вроде этого:
#include <variant>
#include <type_traits>
#include <iostream>
// ...
std::variant<A*, B*, C*> fetch_from_id(int const id) {
switch (objs[id]->type) {
case Type::A: return getA(id);
case Type::B: return getB(id);
default: return getC(id);
}
}
void test() {
A a(1); B b(2); C c(3);
for (int id = 0; id < count; id )
std::visit([] (autoamp;amp; v) {
using type = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<type, A*>) {
// For A
std::cout << v->prop1 << std::endl;
}
if constexpr (std::is_same_v<type, B*>) {
// For B
std::cout << v->prop1 << std::endl;
}
if constexpr (std::is_same_v<type, C*>) {
// For C
std::cout << v->prop1 << std::endl;
}
}, fetch_from_id(id));
}
Если вы хотите сами убедиться, работает ли это:
Комментарии:
1. В настоящее время, поскольку вы используете один и тот же код для обоих случаев,
[] (autoamp;amp; v) { std::cout << v->prop1 << std::endl; }
было бы достаточно.2. Помощник
overloaded
изstd::visit
примера является хорошей альтернативойif constexpr
.3. Спасибо! Я полагаю, что нет никакого способа избавиться от случаев переключения типов в коде fetch_from_id?
Ответ №2:
Приведенное ниже позволит вам очень легко извлечь другие свойства, используя лямбды:
#include <variant>
#include <string>
#include <iostream>
enum class Type {A, B, C};
struct Object {Type type;};
Object* objs[3];
int count = 0;
#define addobj(ptr) objs[count ] = (Object*)ptr
struct A {
Type type;
int prop1;
A(int v) : type(Type::A), prop1(v) {addobj(this);}
};
struct B {
Type type;
int prop1;
B(int v) : type(Type::B), prop1(v) {addobj(this);}
};
struct C {
Type type;
int prop1;
C(int v) : type(Type::C), prop1(v) {addobj(this);}
};
A* getA(int id) {return (A*)objs[id];}
B* getB(int id) {return (B*)objs[id];}
C* getC(int id) {return (C*)objs[id];}
using var_t = std::variant<A*,B*,C*>;
var_t fetch_from_id(int const id) {
switch (objs[id]->type) {
case Type::A: return getA(id);
case Type::B: return getB(id);
default: return getC(id);
}
}
// straightforward lambdas for extracting any property from your objects
auto get_prop1 = [](autoamp;amp; t){ return t->prop1;};
auto get_prop2 = [](autoamp;amp; t){ return t->prop2;};
auto get_prop3 = [](autoamp;amp; t){ return t->prop3;};
int main()
{
A a(1); B b(2); C c(3);
// print prop1
for (int id=0; id<3; id ) std::cout << std::visit(get_prop1, fetch_from_id(id)) << " ";
std::cout << std::endl;
// print prop2
// for (int id=0; id<3; id ) std::cout << std::visit(get_prop2, fetch_from_id(id)) << " ";
// std::cout << std::endl;
// print prop3
// for (int id=0; id<3; id ) std::cout << std::visit(get_prop3, fetch_from_id(id)) << " ";
// std::cout << std::endl;
}
Живой код здесь