Подтвердить что ты не робот

Как я могу упростить этот избыточный код на С++?

Существует два существующих класса: один SrcField, который возвращает конкретное значение типа, а другой представляет собой объединение DSTField, которое определяет соответствующий тип данных.

class SrcField
{
public:
    signed char     GetInt8();
    unsigned char   GetUInt8();
    short           GetInt16();
    unsigned short  GetUInt16();
    int             GetInt32();
    unsigned int    GetUInt32();
    float           GetFloat();
    double          GetDouble();
    bool            GetBool();
    DataType        GetType();
private:
    DataType m_type;
    DSTField m_data;
};

union DSTField
{
    signed char    m_int8;
    unsigned char  m_uint8;
    short          m_int16;
    unsigned short m_uint16;
    int            m_int32;
    unsigned int   m_uint32;
    float          m_float;
    double         m_double;
    bool           m_bool;
};

Когда я использую оба класса, приложение выглядит следующим образом. Это очень избыточно; есть ли хороший способ упростить его, например, шаблоны, общее программирование и т.д.

int main()
{
    SrcField sf;
    DSTField df;

    switch(sf.GetType())
    {
    case TYPE_INT8:
        df.m_int8 = sf.GetInt8();
        break;
    case TYPE_UINT8:
        df.m_uint8 = sf.GetUInt8();
        break;
    case TYPE_INT16:
        df.m_int16 = sf.GetInt16();
        break;
    case TYPE_UINT16:
        df.m_uint16 = sf.GetUInt16();
        break;
    case TYPE_INT32:
        df.m_int32 = sf.GetInt32();
        break;
    case TYPE_UINT32:
        df.m_uint32 = sf.GetUInt32();
        break;
    case TYPE_FLOAT:
        df.m_float = sf.GetFloat();
        break;
    case TYPE_DOUBLE:
        df.m_double = sf.GetDouble();
        break;
    case TYPE_BOOL:
        df.m_bool = sf.GetBool();
        break;
    default:
        break;
    }
}
4b9b3361

Ответ 1

Используя std::variant, ваш код будет выглядеть так:

#include <iostream>
#include <variant>

typedef std::variant<
    signed char,
    unsigned char,
    short,
    unsigned short,
    int,
    unsigned int,
    float,
    double,
    bool
> SrcField, DSTField;

int main()
{
    SrcField sf(97.0f);
    DSTField df;

    df = sf;

    if(auto pval = std::get_if<float>(&df))
      std::cout << "variant value: " << *pval << '\n'; 
    else 
      std::cout << "failed to get value!" << '\n';
}

Примечание. Поскольку это С++ 17, для предыдущих версий я рекомендую использовать boost::variant, boost::any или реализацию класса Any только для заголовка (например, я использую один на основе this в моем проекте)

Ответ 2

Вы сказали, что вы не можете изменить SrcField, поэтому хорошим решением может быть использование посетителя. Резервный код все еще существует, но он присутствует только один раз. Смотрите это:

template<typename Visitor>
constexpr void
visitField(Visitor&& visitor, SrcField& field)
{
    switch(field.GetType())
    {
    case TYPE_INT8:
        visitor(field.GetInt8());
        break;
    case TYPE_UINT8:
        visitor(field.GetUInt8());
        break;
    ....
    default:
        throw std::runtime_error("invalid type");
}

Таким образом, вы можете использовать значения простым способом:

int main()
{
    SrcField field;
    visitField([](auto value) {
        if constexpr(std::is_same<decltype(value), double>::value)
            std::cout << "Hey, double here!\n";
        else if constexpr(std::is_same<decltype(value), bool>::value)
            std::cout << "True or false?\n";
        else
            std::cout << "Other types\n";
        std::cout << value << '\n';
    }, field);
}

В этом случае я использовал возможность if constexpr из С++ 17. Другая возможность использует перегрузка лямбда

Вы можете найти более полный пример здесь, на godbolt

Примечание. Как вы можете видеть, я вообще не использовал DSTField. Если вам действительно нужно использовать DSTField, вы можете использовать аналогичный подход:

template<typename T>
constexpr void
setField(DSTField& dstField, T value)
{
    static_assert(std::is_arithmetic<T>::value,
                  "value must be an arithmetic type");

    if constexpr(std::is_same<T, signed char>::value)
        dstField.m_int8 = value;
    else if constexpr(std::is_same<T, unsigned char>::value)
        dstField.m_uint8 = value;
    ...
}

который можно использовать с чем-то вроде

DSTField dest;
setField(dest, 4.f);

Другое примечание: Я отметил функцию visitField как constexpr, но я не могу быть уверен, что вы можете использовать это. Действительно, если SrcField::GetType может выполняться только во время выполнения, visitField никогда не будет выполняться во время компиляции.

Другое примечание: Я не знаю, может ли это зависеть от вашего кода или нет, но вы должны помнить, что не может быть уверенным, что signed char является std::int8_t (как для большинства других типов, очевидно). Вы должны использовать фиксированные ширины целочисленных типов, если вы хотите, чтобы ваш код работал должным образом на внешних архитектурах.