简体   繁体   中英

Iterate over C++ variadic template

I have the following:

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

Is it possible to make the template variadic:

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

You can use recursion for this!

Guessing at your definition of 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>();
}

( live demo )

You can call the same variadic template function with different template arguments:

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();
}

In the first function we explicitly state not only typename FIRST , but also typename SECOND to avoid matching this implementation to createSenderChain<T> calls, since the variadic part may be matched with empty list of types.

As your question does not specify which variant of C++ to use, this solution uses C++17. This means it can avoid the recursion.

We do this via binary folds to resolve the composition of functions. To do this, we first need to convert function objects to objects that can be composed via a binary operation:

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)}; }

this creates a composable function object that composes with * .

Next, we need an object that represents "construct a T on the heap", without specifying how it is done by the object:

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

maker returns a function object that represents callilng make_unique<T> on a set of arguments to be provided later. I could do this with raw pointers, but I refuse.

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

and done. Note that I use unique_ptr<Sender> s instead of Sender* s, because I refuse to provide crap code you shouldn't use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM