Home > Software engineering >  How can I instantiate template of multiple enums concisely?
How can I instantiate template of multiple enums concisely?

Time:11-29

If I want to instantiate all combinations of Type1, Type2 and Type3 for template function Caclculate, it seems that I have to code 72 lines. Is there any way to simplify these code?

#include <iostream>
#include <map>
#include <tuple>
#include <functional>

enum Type1 {
    kType1_1,
    kType1_2,
    kType1_3,
    kType1_4,
    kType1_5,
    kType1_6,
};

enum Type2 {
    kType2_1,
    kType2_2,
    kType2_3,
};

enum Type3 {
    kType3_1,
    kType3_2,
    kType3_3,
    kType3_4,
};

template <Type1 t1, Type2 t2, Type3 t3>
int Caclculate() {
    std::cout << static_cast<int32_t>(t1) << " " << static_cast<int32_t>(t2) << std::endl;
    return 0;
}

int CalculateHandler(Type1 t1, Type2 t2, Type3 t3) {
    static std::map<std::tuple<Type1, Type2, Type3>, std::function<int()>> func = {
        std::make_pair(std::make_tuple(Type1::kType1_1, Type2::kType2_1, Type3::kType3_1), Caclculate<Type1::kType1_1, Type2::kType2_1, Type3::kType3_1>),
        std::make_pair(std::make_tuple(Type1::kType1_1, Type2::kType2_2, Type3::kType3_1), Caclculate<Type1::kType1_1, Type2::kType2_2, Type3::kType3_1>),
    };
    return func[std::make_tuple(t1, t2, t3)]();
}

int main() {
    CalculateHandler(kType1_1, kType2_2, kType3_1);
}

CodePudding user response:

use for(int i;...) then, use every <> for every single time to have a new variable. then your code will be so much shorter. note: you have to use <> in for{}; otherwise it won't work. contact me if you need more clarification about it.

CodePudding user response:

You can do this, but the general solution might be longer than just typing it out.

We want to plug the enums into template arguments and get a list of combinations out:

using Input1 = Sequence<Type1_1, Type1_2>;
using Input2 = Sequence<Type2_1, Type2_2>;
using Input3 = Sequence<Type3_1, Type3_2>;

using Output = Combine<Input1, Input2, Input3>;
/* result: CombinationSet<
    Combination<Type1_1, Type2_1, Type3_1>,
    Combination<Type1_1, Type2_1, Type3_2>,
    Combination<Type1_1, Type2_2, Type3_1>,
    // etc
*/

So, first, define the input and output types:

template <typename T, T... Cs>
struct Sequence {
    using Type = T; //
};

// represents one iteration of Combined lists
template <typename, typename, typename>
struct Combination;

// a set of Combinations
template <typename... Ts>
struct CombinationSet;

Now, it's possible to define the first level: turn two constants and a sequence into a set of combinations. We can deduce the types of the constant and sequence through partial specialization:

template <typename, typename, typename>
struct CombineImpl;

template <typename T1, typename T2, typename T3, T1 C1, T2 C2, T3... C3s>
struct CombineImpl<std::integral_constant<T1, C1>, std::integral_constant<T2, C2>, Sequence<T3, C3s...>>
{
    using Type = CombinationSet<Combination<std::integral_constant<T1, C1>, std::integral_constant<T2, C2>, std::integral_constant<T3, C3s>>...>;
};

/* example: 
       CombineImpl<
           std::integral_constant<Type1, Type1_1>,
           std::integral_constant<Type2, Type2_1>,
           Sequence<Type3, Type3_1, Type3_2>
       >
   gets turned into:
       CombinationSet<
           Combination<
               std::integral_constant<Type1, Type1_1>,
               std::integral_constant<Type2, Type2_1>,
               std::integral_constant<Type2, Type2_1>
           >,
           Combination<
               std::integral_constant<Type1, Type1_1>,
               std::integral_constant<Type2, Type2_1>,
               std::integral_constant<Type2, Type2_2>
           >
       >
*/

Now the next level: if we have a constant and two sequences, we can call CombineImpl<C, C, S> multiple times and get a bunch of CombinationSets. BUT, we need a way to concatenate all the sets:

template <typename T1, typename T2, typename T3, T1 I1, T2... I2s, T3... I3s>
struct CombineImpl<std::integral_constant<T1, I1>, Sequence<T2, I2s...>, Sequence<T3, I3s...>> 
{
    using Type = Merge<typename CombineImpl<std::integral_constant<T1, I1>, std::integral_constant<T2, I2s>, Sequence<T3, I3s...>>::Type...>;
              // ^^^^^ how to implement this?
};

I can only think of using a recursive type to flatten the sets. There's probably a better log n solution but I'm not smart enough to make it:

// stub - the first type is the output set, followed by input sets
template <typename... Ts>
struct MergeImpl;

// recursive bit - deduce the combinations in the next set, add to output
template <typename... TOut, typename... Ts, typename... TIn>
struct MergeImpl<CombinationSet<TOut...>, CombinationSet<Ts...>, TIn...> 
{
    using Type = typename MergeImpl<CombinationSet<TOut..., Ts...>, TIn...>::Type;
};

// terminal - when there are no more inputs, expose the output.
template <typename... TOut>
struct MergeImpl<CombinationSet<TOut...>>
{
    using Type = CombinationSet<TOut...>;
};

// type alias to start with an empty output set
template <typename... Ts>
using Merge = typename MergeImpl<CombinationSet<>, Ts...>::Type;

/* example: 
       Merge<
           CombinationSet<
               Combination<
                   std::integral_constant<Type1, Type1_1>,
                   std::integral_constant<Type2, Type2_1>,
                   std::integral_constant<Type2, Type2_1>
               >,
               Combination<
                   std::integral_constant<Type1, Type1_1>,
                   std::integral_constant<Type2, Type2_1>,
                   std::integral_constant<Type2, Type2_2>
               >
           >,
           CombinationSet<
               Combination<
                   std::integral_constant<Type1, Type1_1>,
                   std::integral_constant<Type2, Type2_2>,
                   std::integral_constant<Type2, Type2_1>
               >
           >
        >
    Gets turned into:
        CombinationSet<
           Combination<
               std::integral_constant<Type1, Type1_1>,
               std::integral_constant<Type2, Type2_1>,
               std::integral_constant<Type2, Type2_1>
           >,
           Combination<
               std::integral_constant<Type1, Type1_1>,
               std::integral_constant<Type2, Type2_1>,
               std::integral_constant<Type2, Type2_2>
               >
           Combination<
               std::integral_constant<Type1, Type1_1>,
                   std::integral_constant<Type2, Type2_2>,
                   std::integral_constant<Type2, Type4_1>
               >
        > 

Now with merge defined, we can just add another layer to get all the combinations for three sequences:

template <typename T1, typename T2, typename T3, T1... I1s, T2... I2s, T3... I3s>
struct CombineImpl<Sequence<T1, I1s...>, Sequence<T2, I2s...>, Sequence<T3, I3s...>>
{
    using Type = Merge<typename CombineImpl<std::integral_constant<T1, I1s>, Sequence<T2, I2s...>, Sequence<T3, I3s...>>::Type...>;
};

template <typename T1, typename T2, typename T3>
using Combine = typename CombineImpl<T1, T2, T3>::Type;

The next step is to create the map from a CombinationSet. I'm going to use partial specialization again:

// stub
template <typename>
struct CalculateHandlerImpl;

// specialization - deduce combination types and declare static map
template <typename T1, typename T2, typename T3, T1... C1s, T2... C2s, T3... C3s>
struct CalculateHandlerImpl<CombinationSet<Combination<std::integral_constant<T1, C1s>, std::integral_constant<T2, C2s>, std::integral_constant<T3, C3s>>...>> {
    static std::map<std::tuple<T1, T2, T3>, std::function<int()>> value;
};

// static maps have to be defined out of line
template <typename T1, typename T2, typename T3, T1... C1s, T2... C2s, T3... C3s>
std::map<std::tuple<T1, T2, T3>, std::function<int()>> CalculateHandlerImpl<CombinationSet<Combination<std::integral_constant<T1, C1s>, std::integral_constant<T2, C2s>, std::integral_constant<T3, C3s>>...>>::value = {
    { std::make_tuple(C1s, C2s, C3s), Calculate<C1s, C2s, C3s> }...
};

Finally, write a handler function to tie it all together:

template <typename T1, typename T2, typename T3>
int CalculateHandler(typename T1::Type t1, typename T2::Type t2, typename T3::Type t3) {
    return CalculateHandlerImpl<Combine<T1, T2, T3>>::value[std::make_tuple(t1, t2, t3)]();
}

// maybe a variadic macro can help define this and the enum at the same time
using Type1Seq = Sequence<Type1, 
    kType1_1, 
    kType1_2, 
    kType1_3, 
    kType1_4, 
    kType1_5, 
    kType1_6>;

using Type2Seq = Sequence<
    Type2, 
    kType2_1, 
    kType2_2, 
    kType2_3>;

using Type3Seq = Sequence<Type3,
    kType3_1,
    kType3_2,
    kType3_3,
    kType3_4>;

int main() {
    CalculateHandler<Type1Seq, Type2Seq, Type3Seq>(kType1_1, kType2_2, kType3_1);
}

https://godbolt.org/z/r1f4886dh

Now, you'll notice that there is probably at least 72 lines for all this template code. Plus, it will definitely increase compilation time. But the above would help if the lists grow. And it could save you the trouble of checking that you included all the permutations.

  • Related