1

We have

enum Enum {A,B,C,D,E,F,G,H, NumEnums};

class Base {};

template <Enum...> class Thing : public Base {};

and the function

Base* create (std::list<Enum>& input);

is to create an object of the type that corresponds to input. For example, if input = {A,E,C,G,D};, then the output shall be of type Thing<A,E,C,G,D>* (let's forget the sorting here). Now I know input is obtained during run-time, but by doing a search, the output can be obtained fairly quickly. If Thing had only one parameter (i.e. input has size() one), then the simple

template <int N>
Base* createHelper (const std::list<Enum>& input) {
    const Enum En = static_cast<Enum>(N);
    if (input.front() == En)
        return new Thing<En>;
    return createHelper<N+1>(input);
}

template <>
Base* createHelper<NumEnums> (const std::list<Enum>&) {
    return nullptr;
}

Base* create (const std::list<Enum>& input) {
    return createHelper<0>(input);
}

will do. I tried to generalize the above to any size list (the size would have to be determined during run-time through a similar recursion as above, but that should be fairly quick too). But I got totally lost on how. So I tried to examine the structure of the naïve method:

#include <iostream>
#include <list>
#include <type_traits>
#include <typeinfo>

enum Enum {A,B,C,D,E,F,G,H, NumEnums};

class Base {
    public:
        virtual void print() const = 0;
};

template <Enum...> class Thing : public Base {
    virtual void print() const override {std::cout << typeid(*this).name() << '\n';}
};

Base* create (std::list<Enum>& input) {
    if (input.front() == A) {
        input.pop_front();
        if (input.empty())
            return new Thing<A>;
        else {
            if (input.front() == A) {
                input.pop_front();
                if (input.empty())
                    return new Thing<A,A>;
                else {
                    // ....
                }
            }
            else if (input.front() == B) {
                input.pop_front();
                if (input.empty())
                    return new Thing<A,B>;
                else {
                    // ....
                }
            }               
        }
    }
    else if (input.front() == B) {
        // similar
    }
    // ...
}

int main() {
    std::list<Enum> userInput = {A,B};
    // Wish to construct an instance of Thing<A,B> (efficiently).
    Base* thing = create(userInput);
    thing->print();  // Thing<A,B>
}

I figured I could put this in recursive form. But I cannot think of it. I know the one-dimensional case can be generalized, but I need help here. Or perhaps there is a better way to do it altogether? Once it works, it should not take anymore than a fraction of a second for the create function to return, assuming NumEnums is a decent size and the Thing class has just several template arguments, and not hundreds.

prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • When you say "user input", to me it means the input is defined at run time. You cannot instantiate class templates based on run time data. The logic you are trying will need to deal with N^N cases. That's very inefficient. – R Sahu Aug 08 '15 at 00:19
  • Using recursion, there should be a way. I demonstrated that it is trivial to do so if the user input was just one enum value. So for n values, it should be possible too. It just takes time^n long. If it is 5^5 cases for example, I'm sure it won't take too long for a decent computer to get the job done, less than a second probably. – prestokeys Aug 08 '15 at 00:20
  • @RSahu No, there should be a way. It should definitely be possible. – CinchBlue Aug 08 '15 at 00:22
  • I'm with @RSahu - the possible `Thing` instantiations need to be known at compile time and thus you can't sort this out at runtime for a general list. You can do transformations like this using `boost::mpl` style type-lists as the "list" then has the type information of interest at compile time. – sfjac Aug 08 '15 at 01:07
  • My update has something close to a solution. It just needs some tweaking I think, but I'm strugging to do so. – prestokeys Aug 08 '15 at 01:20

3 Answers3

2

Edit: Turns out, there may be a viable solution here:

  1. Create an associate array between your key and a type factory class.

  2. Dynamically allocate any variables you may need from the type factory once you have it selected (preferably using std::unique_ptr).

The end result may end of looking like this:

std::unordered_map<std::string, type_allocator> str_to_type;
str_to_type["a"] = type_allocator(int); //where type_allocator derives the type of the class from the input variable.
auto variable = str_to_type[input].allocate();
CinchBlue
  • 6,046
  • 1
  • 27
  • 58
1

For specific size, if you compute a single index, you may dispatch at runtime to the correct compile time function:

template <std::size_t N>
std::unique_ptr<Base> make_thing3()
{
    constexpr Enum a2 = Enum(N % NumEnums);
    constexpr Enum a1 = Enum((N / NumEnums) % NumEnums);
    constexpr Enum a0 = Enum((N / NumEnums / NumEnums) % NumEnums);
    return std::make_unique<Thing<a0, a1, a2>>();
}

template <std::size_t... Is>
std::unique_ptr<Base> make_thing3(std::size_t index, std::index_sequence<Is...>)
{
    using maker = std::unique_ptr<Base>();
    maker* fs[] = {&make_thing3<Is>...};

    return fs[index]();
}

std::unique_ptr<Base> make_thing3(const std::array<Enum, 3u>& a)
{
    std::size_t index = 0;
    for (Enum e : a) {
        index *= NumEnums;
        index += e;
    }
    constexpr std::size_t total = NumEnums * NumEnums * NumEnums;

    return make_thing3(index, std::make_index_sequence<total>{});
}

Live Demo

Note: I had to change size of Enum, and reduce my example from make_thing5 to make_thing3 due to compiler limit (not sure if it came from the site or if it is true limits)

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

This solution shows that though the compile-time is long (due to the many template instantiations), the run-time look-up is instant. The compiler limits is 3 enum values as input though. The empty input case is handled too (the return type being Thing<>*).

#include <iostream>
#include <list>

#define show(variable) std::cout << #variable << " = " << variable << std::endl;
enum Enum {A,B,C,D,E,F,G,H, NumEnums};

class Base {
    public:
        virtual void print() const = 0;
};

template <Enum... Es> class Thing : public Base {
    virtual void print() const override {
        const std::list<int> a = {((std::cout << Es << ' '), 0)...};
        std::cout << "\nPack size = " << sizeof...(Es) << '\n';
    }
};

template <int N, int Size, Enum... Es>
struct Create {
    static Base* execute (std::list<Enum>& input) {
        const Enum En = static_cast<Enum>(N);
        if (input.front() == En) {
            input.pop_front();
            return Create<0, Size-1, Es..., En>::execute(input);
        }
        return Create<N+1, Size, Es...>::execute(input);
    }
};

template <int N, Enum... Es>
struct Create<N, 0, Es...> {
    static Base* execute (std::list<Enum>&) {return new Thing<Es...>;}
};

template <int Size, Enum... Es>
struct Create<NumEnums, Size, Es...> {
    static Base* execute (std::list<Enum>&) {return nullptr;}  // This will never be reached
};

template <int Size>
Base* do_create (std::list<Enum>& input) {
    if (input.size() == Size)
        return Create<0, Size>::execute(input);
    return do_create<Size+1>(input);
}

template <>
Base* do_create<4> (std::list<Enum>&) {
    std::cout << "Cannot exceed 3 values.\n";
    return nullptr;
}

Base* create (std::list<Enum>& input) {
    return do_create<0>(input);
}

int main() {
    std::list<Enum> input = {E,A,F};
    Base* thing = create(input);
    thing->print();  // 4 0 5

    input = {};
    create(input)->print();  // Pack size = 0.
}
prestokeys
  • 4,817
  • 3
  • 20
  • 43