Есть ли более чистый способ репликации unordered_map со значениями нескольких типов в С++ (11)

Основная идея — получить unordered_map, в котором хранятся значения разных типов. То, что я пытаюсь сделать, это создать легкодоступный объект для объекта универсального буфера OpenGL. Конечный продукт будет выглядеть примерно так:

UBO ubo = { "Uniforms", "translation", "scale", "rotation", "enabled" };
ubo["scale"]       = 0.5f;
ubo["translation"] = { 0.1f, 0.1f, 0.0f };
ubo["rotation"]    = { 90.0f, 0.0f, 0.0f, 1.0f };
ubo["enabled"]     = GL_TRUE; 

В моем классе UBO я перегрузил оператор []:

struct UBOData;

class UBO
{
    std::unordered_map<std::string,UBOData>
    ...
    public: 
    UBOData &operator[](std::string key)
    {
        UBOData data = new UBOData();
        dataMap.emplace(key, data);
        return data;
    }

    const UBOData& operator[](std::string key)
    {
        return const_cast<UBOData&>(*this)[key];
    }
};

И я использую UBOData для хранения разных типов данных. Вот где моя уверенность ослабевает в свете того, что «правильно» в мире С++.

.
.
.
struct UBOData
{
    enum ReturnType {Undefined, rInt, rFloat, rDouble};

    void       *value;
    ReturnType type;
    int &operator=(int lhs);
    float &operator=(float lhs);
    double &operator=(double lhs);
};

Я усек типы для этого примера, без типов std::array. Также обратите внимание, что я использую void * для хранения значения и говорю мне, что мне нужно переосмыслить свой дизайн. Конечно знаю, поэтому я здесь :)

int &UBOData::operator=(int lhs)
{
    if (type == Undefined) { type = rInt; } else { assert(type == rInt); }
    value = new int(lhs);
    int &rValue = *((int*)value);
    return rValue;
}

float &UBOData::operator=(float lhs)
{
    if (type == Undefined) { type = rFloat; }
    else { assert(type == rFloat); }

    value = new float(lhs);
    float &rValue = *((float*)value);
    return rValue;
}

double &UBOData::operator=(double lhs)
{
    if (type == Undefined) { type = rDouble; }
    else { assert(type == rInt); }

    value = new double(lhs);
    double &rValue = *((double*)value);
    return rValue;
}

Я попытался обернуть void* проверкой типов, но есть ли лучший способ получить карту с несколькими типами без void *?

Примечание. Я использую VS2013 в Windows и clang на Mac и Linux.


person Seth Hays    schedule 20.11.2013    source источник
comment
Рассматривали ли вы полиморфную иерархию типов, хранящую (умные) указатели на базовый класс? Затем вы можете реплицировать (копировать) карту, вызывая метод clone() для каждого элемента. В качестве альтернативы вы можете рассмотреть вариант повышения или повысить любой.   -  person Tony Delroy    schedule 20.11.2013
comment
@TonyD Спасибо за предложение. Это привело меня к следующей ссылке: two-sdg.demon. co.uk/curbralan/papers/ValuedConversions.pdf   -  person Seth Hays    schedule 20.11.2013
comment
Не имеет отношения к вашему вопросу: ваша константная версия operator [] вообще не константа. Это не имеет смысла.   -  person Siyuan Ren    schedule 20.11.2013
comment
@к.р. выглядит как попытка использовать не const версию, которая с треском провалилась.   -  person Yakk - Adam Nevraumont    schedule 20.11.2013
comment
@К.Р. Ты прав. Это ошибка копирования-вставки. Спасибо.   -  person Seth Hays    schedule 20.11.2013


Ответы (2)


Определенно boost::variant. Именно для этого он построен. Вот небольшой пример с использованием вашего кода:

#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>

class UBO
{
    using UBOData = boost::variant<float, std::vector<float>>;
    std::unordered_map<std::string, UBOData> dataMap;
    public: 
    UBO() : dataMap(){}
    UBOData &operator[](const std::string& key)
    {
        return dataMap[key];
    }
};

int main()
{
   UBO ubo;
   ubo["scale"]       = 0.5f;
   ubo["translation"] = std::vector<float>{ 0.1f, 0.1f, 0.0f };
   ubo["rotation"]    = std::vector<float>{ 90.0f, 0.0f, 0.0f, 1.0f };
}

Если вам нужен синтаксис { 0.1f, 0.1f, 0.0f } без ввода std::vector<float> и т. д., вам, вероятно, понадобится какой-то тип прокси, который обрабатывает списки инициализаторов.

person Jesse Good    schedule 20.11.2013
comment
Спасибо. Буду смотреть вариант с форсированием. Может ли boost::variant принимать более двух параметров шаблона? - person Seth Hays; 20.11.2013
comment
@SethHays: Да, до 20. - person Jesse Good; 20.11.2013

boost::variant or boost::any.

Если вы не можете или не хотите использовать boost, прочитайте, что они сделали.

Я бы пошел с variant сам.

person Yakk - Adam Nevraumont    schedule 20.11.2013
comment
Да, из комментария TonyD выше я изучал эти две структуры. Я также нашел другую ссылку, связанную с этой темой, с интересным лакомым кусочком stackoverflow.com/a/6044720/1670072. - person Seth Hays; 20.11.2013