Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do a conversion from enum to type (and use as it in a template) in C++?

This is something that is possible in C# (see How to convert enum to type), but I wonder how I should implement it in C++11 ?

A possible code skeleton (which also shows my intended use) would be :

// classify.h (part of public API) 
// definitions of classes A, B, C cannot be exposed, only a list of names
// (some names might even refer to the same type of object)

enum EAllowedTypes { eA, eB, eC };
class Entity;

bool classifyEntityAs(const Entity& ent, EAllowedTypes type);

_

// classify.cpp
#include "A.h"  // defines class A (derived from a common base class)
#include "BC.h" // defines class BC (derived from a common base class)

template <typename T> 
bool classify(const Entity &) 
{ 
    /* some implementation, with possible specializations */
}

// how to implement this kind of vector/map from enum(int) to existing classes
static const std::vector enumToType</*ClassType*/> =
{
    /* eA */ class A,    
    /* eB */ class BC,
    /* eC */ class BC
};

bool classifyEntityAs(cont Entity& ent, EAllowedTypes type)
{
    return classify<enumToType[type]>(ent);
}

Some clarification based on the comments: 1) classify.h is part of a public API (for third parties/other companies), hence, type cannot be known at compile time. Yet there is no problem to compile the template for all types in EAllowedTypes. The EAllowedTypes is there mainly for communication purposes with the client and to allow future changes to the internal data types (without having to change the public API), so there is no guarantee to a one-on-one mapping. 2) Entity is not a base class of any of A, B, BC, etc. It is just a generic Object with which we want to associate a classification A, B or C. The implementation details of how A, B, etc. is attached should remain hidden.

like image 519
mr_T Avatar asked Oct 03 '16 09:10

mr_T


3 Answers

You could do the following:

template <EAllowedTypes e, typename T>
struct cl {
    static const decltype(e) value = e;
    using type = T;
};

template <typename...>
struct classifier;

template <>
struct classifier<> {
    static bool classify(const Entity& ent, EAllowedTypes type) {
        throw std::logic_error("Whatever... ");
    }
};

template <typename T, typename... Args>
struct classifier<T, Args...> {
    static bool classify(const Entity& ent, EAllowedTypes type) {
        return type == T::value ? 
            ::classify<typename T::type>(ent)
            : classifier<Args...>::classify(ent, type);
    }
};

bool classifyEntityAs(const Entity& ent, EAllowedTypes type) {
    return classifier<cl<eA, A>, cl<eB, BC>, cl<eC, BC>>::classify(ent, type);
}

Which would require you to add a new cl<eX, X> each time you want to add a new possible class. This is basically a "recursive" switch, that will likely be optimized as a simple switch1 by the compiler. The idea here is to link the enum values with their classes using the cl structures.

If you want to store the list of cl outside, you could do something like:

using enumToType = std::tuple<cl<eA, A>, cl<eB, BC>, cl<eC, BC>>;

template <size_t... Idx>
bool classifyEntityAs(const Entity& ent, EAllowedTypes type, 
                      std::index_sequence<Idx...>) {
    return classifier<std::tuple_element_t<Idx, enumToType>...>::classify(ent, type);
}

bool classifyEntityAs(const Entity& ent, EAllowedTypes type) {
    return classifyEntityAs(ent, type, 
        std::make_index_sequence<std::tuple_size<enumToType>{}>{});
}

And then you would only have to modify the definition of enumToType (I am using std::tuple here, but you could have a custom structure that only holds types).


1 This code is basically equivalent to:

switch (type) {
  case eA: return classify<A>(ent);
  case eB: return classify<BC>(ent);
  case eC: return classify<BC>(ent);
  default: throw std::logic_error("Whatever... ");
}

like image 73
Holt Avatar answered Sep 20 '22 12:09

Holt


There is no way to do it directly as you desired as c++ does not support reflection mechanism, and I don't think it will change at least for a while... But... if you know which types should be supported by your classifier you could adjust following code to your use case scenario:

#include <map>
#include <memory>
#include <iostream>


enum EAllowedTypes { eA, eB, eC };

struct A {
  void run() {
    std::cout << "A::run() invoked" << std::endl;
  }
};
struct B {
  void run() {
    std::cout << "B::run() invoked" << std::endl;
  }
};
struct C {
  void run() {
    std::cout << "C::run() invoked" << std::endl;
  }
};

struct ITypeWrapper {
   virtual void run() = 0;
};

template <class T>
struct TypeWrapper: ITypeWrapper {
   void run() {
      T t;
      t.run();
   }
};

template <EAllowedTypes AT, class Type>
struct Mapper { };

template <class... Mappers>
struct Classifier;

template <EAllowedTypes... ATs, class... Types>
struct Classifier<Mapper<ATs, Types>...> {
   static std::map<EAllowedTypes, std::shared_ptr<ITypeWrapper>> m;
};

template <EAllowedTypes... ATs, class... Types>
std::map<EAllowedTypes, std::shared_ptr<ITypeWrapper>> Classifier<Mapper<ATs, Types>...>::m = { { ATs, std::make_shared<TypeWrapper<Types>>() }... };

using ABCClassifier = Classifier<Mapper<eA, A>, Mapper<eB, B>, Mapper<eC, C>>;

int main() {
   ABCClassifier::m[eA]->run();
}

Output:

A::run() invoked

like image 27
W.F. Avatar answered Sep 23 '22 12:09

W.F.


Just drop the enum. Enums represent number. Types are types. Why mix the two? You can do the same only with types and templates!

First, let's change your enum of allowed type to a list of type, which probably suits your problem much better:

using AllowedTypes = std::tuple<TypeA, TypeB, TypeC>;

After that, instead of sending your function classifyEntityAs a number, let's send it a type:

template<typename T>
bool classifyEntityAs(const Entity& ent) { /* ... */ }

Now, sending the type to the classify function is pretty easy:

template<typename T>
bool classifyEntityAs(const Entity& ent) {
    classify<T>(ent);
}

If you want to restrict the type you can send to the function, we will need some meta programming, which will consist of comparing types to check if a type T is one of the type list.

template<typename, typename>
struct is_one_of_list;

// Case where the list is empty. T cannot match to an empty list
template<typename T>
struct is_one_of_list<T, std::tuple<>> : std::false_type {};

// Case where the list is at least one.
// We match the type T with Head.
// If T is not the same as Head, match with Tail list using is_one_of_list
template<typename T, typename Head, typename... Tail>
struct is_one_of_list<T, std::tuple<Head, Tail...>> : std::integral_constant<bool,
    std::is_same<T, Head>::value || is_one_of_list<T, std::tuple<Tail...>>::value
> {};

Okay, now the hard part is done. We can restrict your function with the new type trait we made:

template<typename T, std::enable_if_t<is_one_of_list<T, AllowedTypes>::value>* = nullptr>
bool classifyEntityAs(const Entity& ent) { /* ... */ }

When calling your function, only types from AllowedTypes will be allowed.

You can check a live example on Coliru

like image 40
Guillaume Racicot Avatar answered Sep 20 '22 12:09

Guillaume Racicot