reedts
reedts

Reputation: 143

Store different templated classes in one container without losing information about it's type

I'm currently working on a project where a client part of my application has to be able to create custom templated classes on the server. The server part has to keep track of these created classes and has to remember the types with which the classes has been instantiated. The problem is, that there are around 36 different class-template-combinations that are valid in my application. I'm currently struggling to keep track of these different types in a collection without losing information about my instances.

I'm currently using something like this:

#include <memory>
#include <type_traits>
#include <vector>

enum class data_type : std::uint8_t {
    type_int = 1,
    type_float,
    type_double
};

enum class class_type : std:: uint8_t {
    type_A = 1,
    type_B
};

struct X {
    virtual data_type get_data_type() = 0;
    virtual class_type get_class_type() = 0;
};

template <typename T>
struct A : X {
    data_type get_data_type() override
    {
        if (std::is_same<T, int>::value) {
            return data_type::type_int;
        } else if (std::is_same<T, float>::value) {
            return data_type::type_float;
        } else if (std::is_same<T, double>::value) {
            return data_type::type_double;
        } else {
            /* ... */
        }
    }

    class_type get_class_type() override
    {
        return class_type::type_A;
    }
};

template <typename T>
struct B : X {
    data_type get_data_type() override
    {
        if (std::is_same<T, int>::value) {
            return data_type::type_int;
        } else if (std::is_same<T, float>::value) {
            return data_type::type_float;
        } else if (std::is_same<T, double>::value) {
            return data_type::type_double;
        } else {
            /* ... */
        }
    }

    class_type get_class_type() override
    {
        return class_type::type_B;
    }
};

struct Storage {

    template <typename T, template <typename> class Class>
    void create() {
        Class<T>* t = new Class<T>();
        _classes.push_back(std::unique_ptr<X>(t));
    }

    std::vector<std::unique_ptr<X>> _classes;
};

but I'm wondering if this is the way to go or if there is a more elegant way. Here I would have to always switch through the enums to get the full type out of my Storage class, something like:

switch(_classes.front()->get_class_type()) {
case class_type::type_A:
{
    switch(_classes.front()->get_data_type()) {
    case data_type::type_int:
    {
         /* I finally know that it is A<int> */
    }
/* ... */

Thanks in advance.

Upvotes: 1

Views: 139

Answers (2)

Curious
Curious

Reputation: 21560

You can consider using std::variant and the std::visit pattern

auto var = std::variant<int, float, double>{};
// assign var to value
std::visit([](auto& value) {
    using Type = std::decay_t<decltype(value)>;
    if constexpr (std::is_same<Type, int>{}) {
        // is an int
    } else if (std::is_same<Type, float>{}) {
        // is float
    } else if (std::is_same<Type, double>{}) {
        // is double
    }
}, var);

If the if constexpr looks ugly to you then you can substitute it with a handrolled visitor class as well.

class Visitor {
public:
    void operator()(int& value) { ... }
    void operator()(float& value) { ... }
    void operator()(double& value) { ... }
};

auto var = std::variant<int, float, double>{};
// assign var to value
std::visit(Visitor{}, var);

Upvotes: 3

skypjack
skypjack

Reputation: 50568

As mentioned in the comments to the question, this is a viable approach that could help:

#include<vector>
#include<memory>

struct Counter {
    static int next() {
        static int v = 0;
        return v++;
    }
};

template<typename>
struct Type: Counter {
    static int value() {
        static const int v = Counter::next();
        return v;
    }
};

struct X {
    virtual int get_data_type() = 0;
    virtual int get_class_type() = 0;
};

template <typename T>
struct A : X {
    int get_data_type() override {
        return Type<T>::value();
    }

    int get_class_type() override {
        return Type<A<T>>::value();
    }
};

template <typename T>
struct B : X {
    int get_data_type() override {
        return Type<T>::value();
    }

    int get_class_type() override {
        return Type<B<T>>::value();
    }
};

struct Storage {
    template <typename T, template <typename> class Class>
    void create() {
        Class<T>* t = new Class<T>();
        _classes.push_back(std::unique_ptr<X>(t));
    }

    std::vector<std::unique_ptr<X>> _classes;
};

int main() {
    Storage s;
    s.create<int, A>();

    if(Type<int>::value() == s._classes.front()->get_class_type()) {
        //...
    };
}

See it running on wandbox.

Upvotes: 3

Related Questions