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

How to avoid exponential increase in code when choosing template arguments at run-time

考虑一堆基本类型,Foo,所有类型都具有通用方法的独特实现,Bar()。我可以像这样组合 Foo1Foo2Foo5

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>()) 不同,重要的是 Foo 组合在一起用于对 Do 的单个调用。

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

我在 运行 时 select 一些模板参数的原因是因为一些 Move 不适合某些问题实例(我的程序不适合直到 运行-time 才意识到。

不确定这是您需要的,但是这个怎么样:

Foo *obj1 = nullptr;
Foo *obj2 = nullptr;
Foo *obj3 = nullptr;
Foo *obj4 = nullptr;

     if (cond1) { obj1 = new Foo1; /* do more stuff here */ }
else if (cond2) { obj2 = new Foo5; /* do more stuff here */ }
else if (cond3) { obj4 = new Foo5; /* do more stuff here */ }
else            { obj3 = new Foo3; /* do more stuff here */ }

然后,调用这个函数:

void func(
    Foo *obj1,
    Foo *obj2,
    Foo *obj3,
    Foo *obj4)
{
    if (obj1) obj1->bar();
    if (obj2) obj2->bar();
    if (obj3) obj3->bar();
    if (obj4) obj4->bar();
}

这是一个可能的解决方案:

struct DisabledFoo
{
    void Bar() {}
};

template <
    size_t Combination,
    typename Disabled,
    typename... Foos,
    size_t... Is
>
auto FoosFor(std::index_sequence<Is...>)
{
    return CombinedFoo<
        std::conditional_t<
            static_cast<bool>(Combination & 1 << (sizeof...(Foos) - 1 - Is)),
            Foos,
            Disabled
        >...
    >{};
}

template <
    typename F,
    size_t Combination,
    typename Disabled,
    typename... Foos
>
void FooDo(const F& func) {
    func(
        FoosFor<
            Combination,
            Disabled,
            Foos...
        >(std::make_index_sequence<sizeof...(Foos)>())
    );
}

template <
    typename F,
    typename Disabled,
    typename... Foos,
    size_t... Combinations
>
constexpr auto MakeFooDoers(std::index_sequence<Combinations...>) {
    return std::array{ FooDo<F, Combinations, Disabled, Foos...>... };
}

constexpr size_t constexpr_pow(size_t base, size_t exp)
{
    size_t ret = 1;
    for (size_t i = 0; i < exp; ++i) {
        ret *= base;
    }
    return ret;
}

template <
    typename F,
    typename Disabled,
    typename... Foos
>
constexpr std::array FooDoers{
    MakeFooDoers<
        F,
        Disabled,
        Foos...
    >(std::make_index_sequence<constexpr_pow(2, sizeof...(Foos))>())
};

template <typename F>
constexpr void DoCombination(size_t combination, const F& func) {
    FooDoers<
        F,
        DisabledFoo,
        Foo1,
        Foo2,
        Foo3
    >[combination](func);
}

称之为

DoCombination(0b111, [](auto foo) { Do(foo); });

Live Demo

这尽可能避免了运行时开销,只进行一次间接调用。它通过构建一个 constexpr 函数指针数组来工作,每个函数指针分派到不同的 CombinedFoo,其中虚拟实现 DisabledFoo 用于我们实际上不想调用的 Foos .

基本上,FooDoers 最终看起来像这样:

constexpr std::array FooDoers = {
    [] { Do(CombinedFoo<DisabledFoo, DisabledFoo, DisabledFoo>{}); },
    [] { Do(CombinedFoo<DisabledFoo, DisabledFoo, Foo3>{});        },
    [] { Do(CombinedFoo<DisabledFoo, Foo2,        DisabledFoo>{}); },
    [] { Do(CombinedFoo<DisabledFoo, Foo2,        Foo3>{});        },
    [] { Do(CombinedFoo<Foo1,        DisabledFoo, DisabledFoo>{}); },
    [] { Do(CombinedFoo<Foo1,        DisabledFoo, Foo3>{});        },
    [] { Do(CombinedFoo<Foo1,        Foo2,        DisabledFoo>{}); },
    [] { Do(CombinedFoo<Foo1,        Foo2,        Foo3>{});        }
};

然后我们对其进行索引并调用正确的包装器。

要添加另一个 Foo class,只需将其作为另一个参数添加到 DoCombination 中的 FooDoers

从 run-time 的任何类型列表中选择类型的通用方法:

界面如下:

template <typename ... Types, typename Functor>
void ChooseTypes (const bool chosen[], Functor && f)

其中 chosen[0] = true 表示从列表中选择第一种类型。

在内部,类型像这样转发给仿函数:f()<ChosenTypes...>() 保持顺序。


使用方法:

struct MyFunctor
{
    // data/references can be stored/passed here

    template <typename ... Ts>
    void operator () ()
    {
        // Ts = the chosen types

        // Here's where we get to use them...
    }
};

// later in func/method:

bool chosen[5];

// choose which types you want

MyFunctor f;

// pass data in the functor
// (const/& by constructor)

// Then this will call the functor with the chosen types:
ChooseTypes<T1, T2, T3, T4, T5>(chosen, f);

图书馆代码:

namespace ChooseTypesRecursive // internal use only
{

// CTR = ChooseTypesRecursive

template <int N, typename Functor>
struct CTR
{
    using Next = CTR<N-1, Functor>;

    template <typename CandidateType, typename ... Args>
    static void Ctr (const bool * chosen, Functor & f)
    {
        if (*chosen)
        {
            Next::template Ctr<Args..., CandidateType>
                (++chosen, f);
        }       
        else
        {
            Next::template Ctr<Args...>
                (++chosen, f);
        }
    }
};

template <typename Functor>
struct CTR <0, Functor>
{
    template <typename ... ChosenTypes>
    static void Ctr (const bool *, Functor & f)
    {
        f.template operator()<ChosenTypes...>();
    }
};

} // namespace ChooseTypesRecursive

template <typename ... Types, typename Functor>
void ChooseTypes (const bool chosen[], Functor && f)
{
    constexpr int N = sizeof...(Types);

    using namespace ChooseTypesRecursive;
    
    CTR<N, Functor>::template Ctr<Types...>(chosen, f);
}

解决具体问题:

Live demo