根据运行时参数避免模板实例化的代码重复

Avoiding code duplication for template instantiations depending on runtime parameters

本文关键字:实例化 代码 运行时 参数      更新时间:2023-10-16

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

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

我想使用此库编写一个程序,该程序需要根据运行时信息创建Foo实例。我的问题是,假设有N1N2N3的有效类型可以传递给Param1Param2Param3,当我想实例化Foo的不同特定化时,我可能需要在我的代码中的某个时候创建多达N1 x N2 x N3个分支。例如,假设我在运行时通过用户输入获得三个字符串,每个字符串都决定了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 个功能)。

您可以对每种类型使用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);
}

从 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
}