1

I'm tring to map a key with a "generic" value, and by that I mean that it could be an int, float, char, string, and so on.

In particular I am tring to do this because I receive converted CAN data in the model, and the possible types are (currently) int, float and string.

The first idea was to create an abstract object that could be implemented in different ways (one for each device type), then place it in a container (map or set).

Than I tought it would be more light and efficient to use as value a "generic type", so that as data comes from the CAN bus, the container would be automatically built without creating new ad-hoc objects.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207

1 Answers1

0

std::variant is your option if you can use C++17. I asked the question that is related to your problem. Bellow I've provided the example of how it can be implemented:

#include <iostream>
#include <variant>
#include <assert.h>
#include <string_view>
#include <unordered_map>

enum class ValueType : uint8_t
{
    Undefined       = 0x00,
    Uint16,
    Uint32,
    AsciiString,
};

typedef uint64_t FieldId;

struct FieldInfo
{
    std::string _name;
    ValueType _type;
    uint8_t size;   //  in bytes
};

typedef std::unordered_map<FieldId, FieldInfo> FieldContainer;

static FieldContainer requestFields =
{
    { 0, { "user-id",   ValueType::Uint32, sizeof (uint32_t) } },
    { 1, { "group-id",  ValueType::Uint16, sizeof (uint16_t)} },
    { 2, { "user-name", ValueType::AsciiString, 0} },
};

std::variant<uint8_t, uint32_t, std::string_view> getValue(ValueType type,
                                                           const uint8_t* data,
                                                           size_t length)
{
    if (type == ValueType::Uint32)
    {
        assert(length == sizeof(uint32_t));
        return *reinterpret_cast<const uint32_t*>(data);
    }
    if (type == ValueType::Uint16)
    {
        assert(length == sizeof(uint16_t));
        return *reinterpret_cast<const uint16_t*>(data);
    }
    else if (type == ValueType::AsciiString)
    {
        return std::string_view(reinterpret_cast<const char*>(data), length);
    }

    return static_cast<uint8_t>(0);
}


int main(int argc, char *argv[])
{
    const uint8_t arr[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x4f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x21};
    size_t length = sizeof(arr);

    const auto value0 = getValue(requestFields[0]._type, arr, requestFields[0].size);
    std::visit([](auto&& arg)
                    {
                        std::cout << "value-0: " << arg << std::endl;
                    }, value0);


    const auto value1 = getValue(requestFields[2]._type, arr, length);
    std::visit([](auto&& arg)
                    {
                        std::cout << "value-1: " << arg << std::endl;
                    }, value1);

    return 0;
}

Output:

value-0: 1819043144
value-1: Hello StackOverflow!

UPD.0: I didn't use std::map here, but I think it is not big deal just to use std::variant as value of that container.

slinkin
  • 375
  • 3
  • 15