Есть ли способ std::variant и std::visit заменить старую устаревшую отправку для неаккуратного кода?

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

Если вы хотите сами убедиться, работает ли это:

Demo

Комментарии:

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

Живой код здесь