0

The basic idea is to get a unordered_map that stores values of different types. What I am trying to do is to create an easy accessible object to a OpenGL Uniform Buffer Object. The end product would look something like:

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; 

In my UBO class I have overloaded operator[]:

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

And I am using UBOData to store different data types. This is where my confidence wanes in the light of what is "right" in the c++ world.

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

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

I have truncated the types for this example, no std::array types. Also notice I am using a void * to store the value and tell me I need to rethink my design. Of course I do that's why I am here :)

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

I've attempted to wrap the void* with type checking but is there a better way to get a multi-type map without void *?

Note: I am using VS2013 on Windows and clang on Mac and Linux.

Seth Hays
  • 311
  • 2
  • 11
  • Have you considered having a polymorphic hierarchy of types, storing (smart) pointers to the base class? You can then "replicate" (copy) the map by having it invoke a `clone()` method on each element. Alternatively, you might consider boost variant or boost any. – Tony Delroy Nov 20 '13 at 02:41
  • @TonyD Thanks for the suggestion. It lead me to find this link: http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf – Seth Hays Nov 20 '13 at 03:18
  • 1
    Unrelated to your question: your const version of `operator []` is not const at all. It doesn't make sense. – Siyuan Ren Nov 20 '13 at 03:21
  • @c.r. looks like an attempt to use the non `const` version that failed miserably. – Yakk - Adam Nevraumont Nov 20 '13 at 03:37
  • @C.R. You are right. That is a copy paste error. Thanks. – Seth Hays Nov 20 '13 at 03:42

2 Answers2

2

boost::variant or boost::any.

If you cannot or will not use boost, read what they did.

I would go with variant myself.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Yes from TonyD's comment above I was looking into those two structure. I also found another link that ties into this topic with an interesting tidbit http://stackoverflow.com/a/6044720/1670072 – Seth Hays Nov 20 '13 at 03:45
0

Definitely boost::variant. That is what its built for. Here is a small example using your code:

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

If you want the { 0.1f, 0.1f, 0.0f } syntax without typing std::vector<float>, etc, you probably would need some type of proxy that handles initializer lists.

Jesse Good
  • 50,901
  • 14
  • 124
  • 166