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