Как избежать экспоненциального увеличения кода при динамическом выборе способов объединения типов с общими интерфейсами - PullRequest
2 голосов
/ 22 марта 2020

Рассмотрим группу фундаментальных типов, Foo, все с уникальными реализациями общего метода, Bar(). Я могу комбинировать Foo1, Foo2, Foo5 следующим образом:

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);. Пример решения с if s и switch s для решения 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 случаев, которые я бы предпочел не поддерживать.

Я считаю, что нет способа избежать экспоненциального роста исполняемого файла, но есть ли способ избежать этого в коде ( без внесения существенных недостатков в метод Bar [добавлено из правки])? Или существует известное решение / альтернатива для этой общей проблемы c?

РЕДАКТИРОВАТЬ:

Для ясности: Do(Foo1); Do(Foo2); не совпадает с Do(CombinedFoo<Foo1, Foo2>()), и что крайне важно, чтобы Foo s были объединены для одного вызова Do.

Для тех, кто ненавидит абстракцию, реальная проблема заключается в оптимизации задачи NP-Hard где мои Foo с действительно Generator с фундаментальных Move с, которые могут редактировать мое решение, это затем отправляется в различные Solver с, например SolverSimulatedAnnealing. Если бы мне нужно было отправлять только один генератор за один раз, тогда мои решатели выполняли бы итерации, где они большую часть времени проводили бы в замешательстве (хорошо известно, что такой же тип перемещения неоднократно дает этот эффект).

Причина, по которой у меня проблема с опциями, заключается в том, что некоторые из моих Solver не могут правильно использовать некоторые из Move, в зависимости от экземпляра Problem.

Ответы [ 2 ]

2 голосов
/ 22 марта 2020

Не уверен, что это то, что вам нужно, но что по этому поводу:

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();
}
1 голос
/ 22 марта 2020

Вот возможное решение:

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 используется для Foo, которые мы на самом деле не хотим вызывать.

По сути, 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, просто добавьте его в качестве другого параметра для FooDoers в DoCombination.

...