Итерировать по C++ вариационному шаблону

У меня есть следующее:

template<typename FIRST, typename SECOND>
Sender *createSenderChain() {
    return new FIRST(new SECOND());
}

Можно ли сделать шаблон разнообразным:

template<typename FIRST, typename ...Args>
Sender *createSenderChain() {
    return new FIRST(new SECOND(new THIRD(new ...))  <-- This is the pattern I want, 
                                                         but how should it be done 
                                                         using the args list?
}

3 ответа

Решение

Вы можете использовать рекурсию для этого!

Гадать по вашему определению Sender:

struct Sender { ~Sender() {} };

struct A : Sender { A(Sender* = nullptr) {} };
struct B : Sender { B(Sender* = nullptr) {} };
struct C : Sender { C(Sender* = nullptr) {} };


// Base case
template <typename T>
Sender* createSenderChain()
{
    return new T();
}

// Recursive case
template <typename T1, typename T2, typename ...Ts>
Sender* createSenderChain()
{
    return new T1(createSenderChain<T2, Ts...>());
}

int main()
{
    auto ptr = createSenderChain<A, B, C>();
}

( живое демо)

Вы можете вызывать одну и ту же функцию шаблона переменной с различными аргументами шаблона:

template<typename FIRST, typename SECOND, typename ...Args>
Sender* createSenderChain() {
    return new typename FIRST(createSenderChain<SECOND, Args...>());
}

template<typename FIRST>
Sender* createSenderChain() {
    return new typename FIRST();
}

В первой функции мы явно указываем не только typename FIRST, но также typename SECOND чтобы избежать соответствия этой реализации createSenderChain<T> вызовов, так как часть переменной может быть сопоставлена ​​с пустым списком типов.

Поскольку в вашем вопросе не указано, какой вариант C++ использовать, в этом решении используется C++17. Это означает, что он может избежать рекурсии.

Мы делаем это через двоичные сгибы, чтобы разрешить композицию функций. Для этого нам сначала нужно преобразовать функциональные объекты в объекты, которые могут быть составлены с помощью бинарной операции:

template<class F>
struct compose_t {
  F f;
  template<class Lhs, class Rhs>
  auto operator*( compose_t<Lhs> lhs, compose_t<Rhs> rhs ) {
    auto r =
      [lhs = std::move(lhs).f, rhs = std::move(rhs).f](auto&&...args)
      ->decltype(auto)
      { return lhs(rhs(decltype(args)(args)...)); }
    return compose_t<decltype(r)>{ std::move(r) };
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args){
    return f(std::forward<Args>(args)...);
  }
};
template<class F>
compose_t<F> compose(F f) { return {std::forward<F>(f)}; }

это создает составной функциональный объект, который создает *,

Далее нам нужен объект, который представляет "создать T в куче", не указывая, как это делается объектом:

template<class T>
auto maker() {
  return [](auto&&...args) {
    return std::make_unique<T>( decltype(args)(args)...) )
  };
}

maker возвращает объект функции, представляющий callilng make_unique<T> на набор аргументов, которые будут предоставлены позже. Я мог бы сделать это с помощью сырых указателей, но я отказываюсь.

template<typename ...Args>
std::unique_ptr<Sender> createSenderChain() {
  return (compose( maker<Args>() ) * ...)();
}

и сделано. Обратите внимание, что я использую unique_ptr<Sender>с вместо Sender*s, потому что я отказываюсь предоставлять код, который вы не должны использовать.

Другие вопросы по тегам