Consuming parameter packs recursively in C++

  • A+
Category:Languages

I want to come up with a Lisp-like cons list implementation in C++. I will give you my attempt first.

template <typename E1, typename E2> struct pair {      constexpr pair() :first{E1{}}, second{E2{}}, empty{true}      {}      constexpr pair(E1 const &f, E2 const &s)     :first{f}, second{s}, empty{false}      {}      E1 first;     E2 second;     bool empty; };  template <typename Head, typename Tail> struct cons{     constexpr cons() :_cons{pair<Head, Tail>{}}     {}      constexpr cons(Head h, Tail t) :_cons{pair<Head, Tail>{h, t}}     {}      template <typename... Args>     constexpr cons(Args... args)     {         // I want cons(1, 2, 3) to expand into below call         // in main and cons(1, 2, 3, 4) to expand into         // cons{1, cons{2, cons{3, cons{4, cons<int, int>()}}}}     }      constexpr auto car() {         return _cons.first;     }      constexpr auto cdr() {         return _cons.second;     }      pair<Head, Tail> _cons; };  int main(){     constexpr cons c{1, cons{2, cons{3, cons<int, int>{}}}}; } 

Basically, I want to fill in the part below that implements a variadic constructor.

template <typename... Args> constexpr cons(Args... args) {     // I want cons(1, 2, 3) to expand into below call     // in main and cons(1, 2, 3, 4) to expand into     // cons{1, cons{2, cons{3, cons{4, cons<int, int>()}}}} } 

I don't know how can I process parameter packs in such a recursive way. If an initializer_list solution is better, I can look at that as well. In the end, I want to be able to do:

cons c1 = {1, 2, 3, 4}; 

and it will become

cons{1, cons{2, cons{3, cons{4, cons<int, int>()}}}}; 

Here is my solution I came up with after asking the question, it is in the make_list function.

My Solution


Not being familiar with Lisp, I went by your description as much as I could. You write helper functions make_cons and delegate the construction to the move constructor

template<typename, typename> struct cons;  template<typename U> auto make_cons(U u) {     return cons<U, cons<U, U>>{u, {}}; }  template<typename U, typename... Args> auto make_cons(U u, Args... args) {     return cons<U, decltype(make_cons(args...))>{u, make_cons(args...)}; }  template<typename H, typename T> struct cons {     constexpr cons() :_cons{pair<H, T>{}} {}     constexpr cons(H h, T t) :_cons{pair<H, T>{h, t}} {}     template<typename... Args>     constexpr cons(Args... args) : cons(make_cons(args...)) {}      pair<H, T> _cons; };  template<typename U, typename... Args> cons(U, Args...) -> cons<U, decltype(make_cons(std::declval<Args>()...))>;  template<typename U> cons(U) -> cons<U, cons<U, U>>; 

By your example, I'm assuming you want to have as the last term be cons<U, U> where U is the same type as the terms coming before. You use it as

auto c1 = make_cons(1, 2, 3, 4); cons c2 = {1, 2, 3, 4}; print<decltype(c1)> p1; print<decltype(c2)> p2; 

Live (read the error message to see the resulting type)

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: