#include <string>
#include <iostream>
#include <iomanip>
#include <utility>
using namespace std;

// Wrapper for heap-allocated double
struct D {
    D(double x = -1.0) : p{new double(x)} { cout << "(ctor:" << *p << ") "; }
    ~D() { cout << "(dtor:" << (p ? *p : 0) << ") "; delete p; }

    D(const D& d) : p{new double(*d.p)} { cout << "(copy:" << *p << ") "; }
    D(D&& d) : p{d.p} { d.p = nullptr; cout << "(move:" << *p << ") "; }

    D& operator=(const D& d) = delete;
    D& operator=(D&& d) = delete;

    operator double&() { return *p; }
    operator const double&() const { return *p; }

    double* p;
};

// 1. Basic variadic template using template recursion

void print_v1() { cout << '\n'; } // base case for template recursion

template <typename T, typename... MoreTs>    // template parameter pack
void print_v1(T arg0, MoreTs... more_args) { // function parameter pack
    cout << arg0 << ' ';
    // pack expansion of a pattern containing function parameter pack
    print_v1(more_args...);
}

// 2. Function parameters as forwarding references to reduce copying

void print_v2() { cout << '\n'; }

template <typename T, typename... MoreTs>
void print_v2(T&& arg0, MoreTs&&... more_args) { // forwarding reference
    cout << arg0 << ' ';
    // use std::forward on each arg in the parameter pack
    print_v2(std::forward<MoreTs>(more_args)...);
}

// 3. Fold expression

template <typename... Types>
void print_v3(Types&&... args) {
    // Binary left fold without printing spaces
    (cout << ... << args) << '\n';

    // Unary right fold with comma operator to print spaces
    ((cout << args << ' '), ...) << '\n';
}

int main() {
    string s { "Hi" };

    print_v1(s, "ABC", 45, string{"xyz"});
    cout << '\n';
    print_v1(s, "ABC", 45, string{"xyz"}, D{3.14});

    // print_v2(s, "ABC", 45, string{"xyz"}, D{3.14});

    // print_v3(s, "ABC", 45, string{"xyz"}, D{3.14});
}
