在运行时选择模板参数时如何避免代码的指数增长

艾略特

考虑一堆基本类型,Foo全部具有通用方法的唯一实现Bar()我可以将Foo1结合起来Foo2Foo5就像这样:

CombinedFoo<Foo1, Foo2, Foo5> combined_foo;

它使用递归继承CombinedFoo有效地使其与:

class CombinedFoo <Foo1, Foo2, Foo5>
{
    Foo1 foo1;
    Foo2 foo2;
    Foo5 foo5;

public:

    void Bar ()
    {
        foo1.Bar();
        foo2.Bar();
        foo5.Bar();
    }
};

这很方便,但是当我想在运行时选择Foo哪些类型组合(合并为单个对象)以发送给函数时,就会遇到问题,例如:

template <typename Foo> void Do (Foo && foo);

使用ifs和switchs解决3个选项版本的示例解决方案

int result = 0;

if (criteria_for_foo1)
    result += 100;

if (criteria_for_foo2)
    result += 10;

if (criteria_for_foo3)
    result += 1;

switch (result)
{
     case 001 : Do(Foo3());
                  break;

     case 010 : Do(Foo2());
                  break;

     case 011 : Do(CombinedFoo<Foo2, Foo3>());
                  break;

     case 100 : Do(Foo1());
                  break;

     case 101 : Do(CombinedFoo<Foo1, Foo3>());
                  break;

     case 110 : Do(CombinedFoo<Foo1, Foo2>());
                  break;

     case 111 : Do(CombinedFoo<Foo1, Foo2, Foo3>());
                  break;

     default : break; 
}

这些if语句很好,它们呈线性增长,但是switch随着我们有更多选择,该语句呈指数增长。我的实际问题有4个选项,因此我需要处理16个我不想维护的案例。

我相信没有办法避免可执行文件呈指数增长,但是有没有办法在c ++代码中避免这种情况(Bar方法中不引入明显的低效率)?还是有解决此一般问题的已知解决方法/替代方法?

编辑:

为了清楚起见:Do(Foo1); Do(Foo2)与并不相同Do(CombinedFoo<Foo1, Foo2>()),将Foos组合在一起以实现对的单个调用至关重要Do

对于那些想了解现实世界动机的人:这是针对优化问题的,因为我Foo问题实际上是可以编辑我的解决方案Generator的基本问题,Move然后将其发送到各种启发式方法中。如果我一次只发送Generator一次,那么我的求解器将重复数千次相同类型的移动,因此总是无能为力/停留在局部最小值(反复考虑相同类型的移动众所周知)影响)。

我在运行时选择其中一些模板参数的原因是因为某些Move不适用于某些问题实例(我的程序直到运行时才意识到)。

艾略特

经过很多时间,我终于想到了一个相当简单的通用答案。


通用求解器:

#include <type_traits>

// We choose which arguments (Args...)
// to send to the Call method:

template <int N, int N_MAX, typename Caller, typename ... Args>
std::enable_if_t<N == N_MAX>
ChooseTemplateArgumentsRecursive (const bool[])
{
    Caller::template Call<Args...>();
}

template <int N, int N_MAX, typename Caller, typename CandidateArg, typename ... Args>
std::enable_if_t<N != N_MAX>
ChooseTemplateArgumentsRecursive (const bool include[])
{
    if (include[N])
        ChooseTemplateArgumentsRecursive<N+1, N_MAX, Caller, Args..., CandidateArg>(include);
        
    else
        ChooseTemplateArgumentsRecursive<N+1, N_MAX, Caller, Args...>(include);
}

// You only need to call this function:

template <typename Caller, typename ... CandidateArgs>
void ChooseTemplateArguments (const bool include[])
{
    constexpr int N_MAX = sizeof...(CandidateArgs);

    ChooseTemplateArgumentsRecursive<0, N_MAX, Caller, CandidateArgs...>(include);
}

以上适用于c++14或更高版本。如果您仅有权访问,请c++11同时更改两个:

std::enable_if_t</*expression*/>

...至:

typename std::enable_if</*expression*/>::type

如何使用它:

ChooseTemplateArguments<CallerDo, Foo0, Foo1> (include);

因此,这里我们有一个bool数组来决定是否对include参数进行处理。所以,如果include[0]true随后Foo0将被包括在被发送到参数包Caller::CallCall然后,站点上可以选择参数包,并可以根据需要进行操作。您可以Call根据自己的需要设置站点。对于我的示例,它的工作方式如下:

struct CallerDo
{
    template <typename ... Foos>
    static void Call ()
    {
        Do(CombinedFoo<Foos...>());
    }
};

Call 必须这样命名,并且必须是静态成员。其他一切都可以随心所欲。

参数包保持其原始顺序。

如果Call站点需要访问任何数据(例如功能输入),则可以始终将其发送给您的Caller班级/由您的班级管理


最后,这是一个现场演示

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章