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

//////////////////////////////////////////////////////////////////////
// Forwarding reference
//

namespace c2cpp {
    template<typename T> struct remove_reference { using type = T; };
    template<typename T> struct remove_reference<T&> { using type = T; };
    template<typename T> struct remove_reference<T&&> { using type = T; };

    template<typename T>
    using remove_reference_t = typename remove_reference<T>::type;

    template<typename T>
    T&& forward(remove_reference_t<T>& t) noexcept {
        return static_cast<T&&>(t);
    }
}

template <typename T> void f1(T t)   { t += 0.1; }
template <typename T> void f2(T& t)  { t += 0.2; }
template <typename T> void f3(T&& t) { t += 0.3; }
template <typename T> void f4(T&& t) { D d{t}; d += 0.4; }
template <typename T> void f5(T&& t) { D d{std::forward<T>(t)}; d += 0.5; }
template <typename T> void f6(T&& t) { D d{c2cpp::forward<T>(t)}; d += 0.6; }

int main() {

    // 1. f1: we can pass lvalue or rvalue but t is a copy
    cout << "\ntemplate <typename T> void f1(T t) { t += 0.1; }\n";
    {
        D d1 {1.0};  
        f1(d1);  
        f1(D(2.0));  
    }
    cout << '\n';

    // 2. f2: cannot pass rvalue
    cout << "\ntemplate <typename T> void f2(T& t) { t += 0.2; }\n";
    {
        D d1 {1.0};  
        f2(d1);  
        // f2(D(2.0)); // err: cannot bind rvalue to T&
    }
    cout << '\n';  

    // 3. f3: forwarding reference
    cout << "\ntemplate <typename T> void f3(T&& t) { t += 0.3; }\n";
    {
        D d1 {1.0};
        f3(d1);     // lvalue arg: T = D&, so T&& is D&
        f3(D(2.0)); // rvalue arg: T = D,  so T&& is D&&
    }
    cout << '\n';

    // 4. f4: a forwarding reference is always an lvalue
    cout << "\ntemplate <typename T> void f4(T&& t) { D d{t}; d += 0.4; }\n";
    {
        D d1 {1.0};
        f4(d1);     // t is lvalue inside f4
        f4(D(2.0)); // t is also lvalue inside f4
    }
    cout << '\n';

    // 5. f5: perfect-forwarding a forwarding reference
    cout << "\ntemplate <typename T> "
              "void f5(T&& t) { D d{std::forward<T>(t)}; d += 0.5; }\n";
    {
        D d1 {1.0};
        f5(d1);
        f5(D(2.0));
    }
    cout << '\n';

    // 6. f6: forward<T> implementation
    cout << "\ntemplate <typename T> "
              "void f6(T&& t) { D d{c2cpp::forward<T>(t)}; d += 0.6; }\n";
    {
        D d1 {1.0};
        f6(d1);
        f6(D(2.0));
    }
    cout << '\n';
}
