【问题标题】:Avoiding code duplication for template instantiations depending on runtime parameters根据运行时参数避免模板实例化的代码重复
【发布时间】:2019-11-23 13:36:19
【问题描述】:

我正在使用由其他人编写的 C++ 库(不幸的是,imo)实现了几种封装在类中的算法,其行为可以通过作为模板参数传递的参数进行微调(参数通常也是类)。因此,例如可能有这样的类模板:

template<typename Param1, typename Param2, typename Param3>
class Foo {
  // ...
};

我想使用这个库编写一个程序,该程序需要根据运行时信息创建Foo 的实例。我的问题是,假设有N1N2N3 可以为Param1Param2Param3 传递的有效类型,我可能需要创建最多N1 x N2 x N3 分支当我想实例化Foo 的不同特殊化时,我的代码中的某个点。例如,假设我在运行时通过用户输入获得三个字符串,每个字符串决定Foo 的模板参数之一应该是哪种类型,那么我需要做这样的事情:

std::string s1, s2, s3;

// instantiate s1, s2, s3 from user input

if (s1 == "Param1_1" && s2 == "Param2_1" && s3 == "Param3_1") {
  Foo<Param1_1, Param2_1, Param3_1> foo;
} else if (s1 == "Param1_1" && s2 == "Param2_1" && s3 == "Param3_2") {
  Foo<Param1_1, Param2_1, Param3_2> foo;
}

// ...

其中Param1_1Param1 的有效类型等等。我怎样才能更优雅地实现这一点? (理想情况下只使用 C++11 或最多 C++17 功能)。

【问题讨论】:

  • 更优雅?我没有看到如何手动编写所有这些组合的方法,最多可以缩短使用某些宏编写的代码,但如果 that 更“优雅”是值得怀疑的。不过,比这个可能很长的 if/else 链可能是散列:std::unordered_map&lt;std::string, void(*)(/* parameters as needed */),您可能会用一些 lambdas 填充它,例如 map["Param1_1\x1FParam2_1\x1FParam3_1"] = [](/*...*/) { Foo&lt;Param1_1, Param2_1, Param3_1&gt; foo; /* ... */ };
  • 旁注:0x1f 是 ASCII 单位分隔符,如果您愿意,请选择另一个,但用户很难在键盘上输入...
  • jit proposal 可能会让您感兴趣。即使您仍然必须进行调度。
  • 我会选择简单的东西。只需将if 之后的所有内容放入一个通用 lambda,然后使用foo 调用它。喜欢auto continuation = [&amp;, this](auto foo) { ... }; if(...) { continuation(Foo&lt;Param1_1, Param2_1, Param3_1&gt;()); } else if(...) { continuation(Foo&lt;...&gt;()); } ...。对于 C++11,去掉 lambda 并将其放入模板(成员)函数中。

标签: c++ templates


【解决方案1】:

从 C++11 开始,您可以创建一系列函数,每种类型一个,以选择和传输正确的模板参数;最后一个调用的函数(第一个,在下面的例子中)可以创建Foo 对象。

不是很优雅,但可以避免 N1 x N2 x N3 乘法效应。

template <typename ... PrevParams>
void for_s3 (std::string const & s3)
 {
   if ( "Param3_1" == s3 )
      Foo<PrevParams..., Param3_1>{}
   else if ( "Param3_2" == s3 )
      Foo<PrevParams..., Param3_2>{};
   // other cases 
 }    

template <typename ... PrevParams>
void for_s2 (std::string const & s2, std::string const & s3)
 {
   if ( "Param2_1" == s2 )
      for_s3<PrevParams..., Param2_1>(s3);
   else if ( "Param2_2" == s2 )
      for_s3<PrevParams..., Param2_2>(s3);
   // other cases 
 }    

void for_s1 (std::string const & s1, std::string const & s2, std::string const & s3)
 {
   if ( "Param1_1" == s1 )
      for_s2<Param1_1>(s2, s3);
   else if ( "Param1_2" == s1 )
      for_s2<Param1_2>(s2, s3);
   // other cases 
 }

也许更优雅、更灵活一些(您可以轻松添加其他字符串和参数),但运行时成本更高,是一种双功能解决方案

template <typename ... Params>
void select_foo ()
 {
   Foo<Params...>  f;

   // do something with f
 }

template <typename ... PrevParams, typename ... Ts>
void select_foo (std::string const & s, Ts const & ... ts)
 {
   if ( "Param1_1" == s )
       select_foo<PrevParams..., Param1_1>(ts...);
   else ( "Param1_2" == s )
       select_foo<PrevParams..., Param1_2>(ts...);
   // other Param1_x cases
   else if ( "Param2_1" == s )
       select_foo<PrevParams..., Param2_1>(ts...);
   else ( "Param2_2" == s )
       select_foo<PrevParams..., Param2_2>(ts...);
   // other Param2_x cases
   else if ( "Param3_1" == s )
       select_foo<PrevParams..., Param3_1>(ts...);
   else ( "Param3_1" == s )
       select_foo<PrevParams..., Param3_2>(ts...);
   // other Param3_x cases
 }

【讨论】:

    【解决方案2】:

    您可以对每种类型使用std::variant (c++17) 并使用std::visit 进行调度:

    template <typename T> struct Tag{ using type = T;};
    
    std::variant<Tag<T1>, Tag<T2>, Tag<T3>/*..*/> getType(const std::string& s)
    {
        if (s == "Param1") { return Tag<T1>{}; }
        else if (s == "Param2") { return Tag<T2>{}; }
        // ...
    }
    
    void bar(const std::string& s1, const std::string& s2, const std::string& s3)
    {
        auto v1 = getType(s1);
        auto v2 = getType(s2);
        auto v3 = getType(s3);
    
        std::visit([](auto t1, auto t2, auto t3)
                  {
                      Foo<typename decltype(t1)::type,
                          typename decltype(t2)::type,
                          typename decltype(t3)::type> foo;
                      /*..*/
                   }, v1, v2, v3);
    }
    

    【讨论】:

    • 有趣...'Effectively returns std::invoke(std::forward&lt;Visitor&gt;(vis), std::get&lt;is&gt;(std::forward&lt;Variants&gt;(vars))...), where is... is vars.index()....' 问题:我们怎么可能以这种方式获得编译时间常数? vars.index() 本身不是。
    • @Aconcagua 它为每个索引实例化访问者,因为它有条件地调用它们,但实际上只调用它一个。所以在编译时编译器仍然可以看到所有的分支,并且访问者必须兼容所有 27 种组合。
    • @JohannesSchaub-litb 啊,谢谢。因此,最终(内部)与所讨论的原始方法并没有真正的不同,但现在基于索引而不是字符串——只是对我们隐藏了......
    • 这看起来很有趣,我会尝试并接受如果它成功(尽管我并不怀疑你的答案的正确性)。
    猜你喜欢
    • 2017-09-12
    • 1970-01-01
    • 2013-08-05
    • 1970-01-01
    • 2011-09-23
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多