【问题标题】:How can I instantiate template of multiple enums concisely?如何简洁地实例化多个枚举的模板?
【发布时间】:2022-01-05 00:07:34
【问题描述】:

如果我想为模板函数Caclculate实例化Type1、Type2和Type3的所有组合,看来我必须编写72行代码。有什么办法可以简化这些代码?

#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);
}

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    您可以这样做,但一般的解决方案可能比直接输入要长。

    我们想将枚举插入到模板参数中并得到一个组合列表:

    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
    */
    

    所以,首先,定义输入和输出类型:

    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;
    

    现在,可以定义第一级:将两个常量和一个序列转换为一组组合。我们可以通过偏特化推导出常量和序列的类型:

    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>
               >
           >
    */
    

    现在下一级:如果我们有一个常量和两个序列,我们可以多次调用CombineImpl&lt;C, C, S&gt;,得到一堆CombinationSets。但是,我们需要一种连接所有集合的方法:

    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?
    };
    

    我只能考虑使用递归类型来展平集合。可能有更好的log n 解决方案,但我不够聪明,无法做到:

    // 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>
                   >
            > 
    

    现在定义了合并,我们可以添加另一个层来获取三个序列的所有组合:

    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;
    

    下一步是从CombinationSet 创建地图。我将再次使用部分专业化:

    // 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> }...
    };
    

    最后,编写一个处理函数将它们绑定在一起:

    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/WPaPqa4zT

    现在,您会注意到所有这些模板代码可能至少有 72 行。另外,它肯定会增加编译时间。但如果列表增长,上述内容会有所帮助。并且它可以省去您检查是否包含所有排列的麻烦。

    【讨论】:

      【解决方案2】:

      使用 for(int i;...) 然后,每次使用每个 来获得一个新变量。那么你的代码会更短。 注意:你必须在 for{} 中使用 ;否则它将无法正常工作。 如果您需要更多说明,请与我联系。

      【讨论】:

      • 你的意思是这样的吗? func[std::make_tuple(static_cast&lt;Type1&gt;(i), static_cast&lt;Type2&gt;(j), static_cast&lt;Type3&gt;(k))] = Caclculate&lt;static_cast&lt;Type1&gt;(i), static_cast&lt;Type2&gt;(j), static_cast&lt;Type3&gt;(k)&gt;(); 但是编译好像是不可能的。 error: the value of ‘i’ is not usable in a constant expression Caclculate&lt;static_cast&lt;Type1&gt;(i), static_cast&lt;Type2&gt;(j), static_cast&lt;Type3&gt;(k)&gt;();
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 2010-12-09
      • 1970-01-01
      相关资源
      最近更新 更多