#include <iostream>
#include <memory>

struct copyableMovable 
{
    copyableMovable() { std::cout << "\tcopyableMovable()\t"; }
    copyableMovable(const copyableMovable&) noexcept { std::cout << "\tcopyableMovable(const copyableMovable&)\n"; }
    copyableMovable(copyableMovable&&) noexcept { std::cout << "\tcopyableMovable(copyableMovable&&)\n"; }
};

struct onlyMovable 
{
    onlyMovable() { std::cout << "\tonlyMovable()\t"; }
    onlyMovable(onlyMovable&&) { std::cout << "\tonlyMovable(onlyMovable&&)\n"; }
    onlyMovable(const onlyMovable&) = delete; 
};

struct byValue 
{
    byValue(copyableMovable) { std::cout << "byValue(copyableMovable)\n"; }
    byValue(onlyMovable) { std::cout << "byValue(onlyMovable)\n"; }
};
struct byReference 
{
    byReference(copyableMovable&) { std::cout << "byReference(copyableMovable&)\n"; }
    byReference(onlyMovable&) { std::cout << "byReference(onlyMovable&)\n"; }
};
struct byConstReference 
{
    byConstReference(const copyableMovable&) { std::cout << "byConstReference(const copyableMovable&)\n"; }
    byConstReference(const onlyMovable&) { std::cout << "byConstReference(const onlyMovable&)\n"; }
};
struct byRvalueReference 
{
    byRvalueReference(copyableMovable&&) { std::cout << "byRvalueReference(copyableMovable&&)\n"; }
    byRvalueReference(onlyMovable&&) { std::cout << "byRvalueReference(onlyMovable&&)\n"; }
};


template <typename T, typename Arg>
std::unique_ptr<T> without_forward(Arg arg)                                       
{ return std::unique_ptr<T>(new T(arg)); }
 
template <typename T, typename Arg>
std::unique_ptr<T> with_forward(Arg arg)                                       
{ return std::unique_ptr<T>(new T(std::forward<Arg>(arg))); }


int main() 
{
   std::cout << "\tIDEA\nPassing lvalues and rvalues to function that return unique_ptr for them.\nWe have two kinds of objects:\n";
   copyableMovable cm{};	std::cout << " - can be copied and moved along i. e. not restricted at all.\n";
   onlyMovable om{};		std::cout << " - can be moved but cannot be copied, i. e. unique_ptr or streams etc.\n";

   std::cout << "\n\n\n\tWITHOUT FORWARD\n";
   
   std::cout << "BY VALUE\n";
   std::cout << "Lvalue - copyable:\n";	auto val_copy_l = without_forward<byValue>(cm);
   std::cout << "\nLvalue - moveable:\n";	//auto val_move_l = without_forward<byValue>(om);
   std::cout << "Impossible, needs copy constructor.\n";
   std::cout << "\nRvalue - copyable:\n";	auto val_copy_r = without_forward<byValue>(copyableMovable{});
   std::cout << "\nRvalue - moveable:\n";	//auto val_move_r = without_forward<byValue>(onlyMovable{});
   std::cout << "Impossible, needs copy constructor.\n\n";
   
   std::cout << "BY REFERENCE\n";
   std::cout << "Lvalue - copyable:\n";	auto ref_copy_l = without_forward<byReference>(cm);
   std::cout << "\nLvalue - moveable:\n";	//auto ref_move_l = without_forward<byReference>(om);
   std::cout << "Impossible, needs copy constructor\n";
   std::cout << "\nRvalue - copyable:\n";	auto ref_copy_r = without_forward<byReference>(copyableMovable{});
   std::cout << "\nRvalue - moveable:\n";	auto ref_move_r = without_forward<byReference>(onlyMovable{});
   
   std::cout << "BY CONST REFERENCE\n";
   std::cout << "\nLvalue - copyable:\n";	auto cref_copy_l = without_forward<byConstReference>(cm);
   std::cout << "\nLvalue - moveable:\n";	//auto cref_move_l = without_forward<byConstReference>(om);
   std::cout << "Impossible, needs copy constructor\n";
   std::cout << "\nRvalue - copyable:\n";	auto cref_copy_r = without_forward<byConstReference>(copyableMovable{});
   std::cout << "\nRvalue - moveable:\n";	auto cref_move_r = without_forward<byConstReference>(onlyMovable{});

   std::cout << "BY RVALUE REFERENCE\n";
   std::cout << "\nLvalue - copyable:\n";	//auto rref_copy_l = without_forward<byRvalueReference>(copyableMovable{});
   std::cout << "Lvalue - moveable:\n";	//auto rref_move_l = without_forward<byRvalueReference>(onlyMovable{});
   std::cout << "Rvalue - copyable:\n";	//auto rref_copy_r = without_forward<byRvalueReference>(copyableMovable{});
   std::cout << "Rvalue - moveable:\n";	//auto rref_move_r = without_forward<byRvalueReference>(onlyMovable{});
   std::cout << "Evarything impossible: they all need an rvalue, but receive an lvalue.\n";
   
   std::cout << "\nWithout forward - short summary:\nPassing by value is impossible for onlyMovable."
		"\nPassing by lvalue reference is impossible for onlyMovable rvalues."
		"\nPassing by rvalue does not work at all.\n";

 
   std::cout << "\n\n\n\tWITH FORWARD\n";
   
   std::cout << "BY VALUE\n";
   std::cout << "Lvalue - copyable:\n";	auto val_copy_lf = with_forward<byValue>(cm);
   std::cout << "\nLvalue - moveable:\n";	//auto val_move_lf = with_forward<byValue>(om);
   std::cout << "Impossible, needs copy constructor.\n";
   std::cout << "\nRvalue - copyable:\n";	auto val_copy_rf = with_forward<byValue>(copyableMovable{});
   std::cout << "\nRvalue - moveable:\n";	auto val_move_rf = with_forward<byValue>(onlyMovable{});
    
   std::cout << "\nBY REFERENCE\n";
   std::cout << "Lvalue - copyable:\n";	//auto ref_copy_lf = with_forward<byReference>(cm);
   std::cout << "Lvalue - moveable:\n";	//auto ref_move_lf = with_forward<byReference>(om);
   std::cout << "Rvalue - copyable:\n";	//auto ref_copy_rf = with_forward<byReference>(copyableMovable{});
   std::cout << "Rvalue - moveable:\n";	//auto ref_move_rf = with_forward<byReference>(onlyMovable{});
   std::cout << "All impossible - lvalue needed, not rvalue.\n";
  
   std::cout << "\nBY CONST REFERENCE\n";
   std::cout << "\nLvalue - copyable:\n";	auto cref_copy_lf = with_forward<byConstReference>(cm);
   std::cout << "\nLvalue - moveable:\n";	//auto cref_move_lf = with_forward<byConstReference>(om);
   std::cout << "Impossible, needs copy constructor\n";
   std::cout << "\nRvalue - copyable:\n";	auto cref_copy_rf = with_forward<byConstReference>(copyableMovable{});
   std::cout << "\nRvalue - moveable:\n";	auto cref_move_rf = with_forward<byConstReference>(onlyMovable{});

   std::cout << "\nBY RVALUE REFERENCE\n";
   std::cout << "\nLvalue - copyable:\n";	auto rref_copy_lf = with_forward<byRvalueReference>(copyableMovable{});
   std::cout << "Lvalue - moveable:\n";	auto rref_move_lf = with_forward<byRvalueReference>(onlyMovable{});
   std::cout << "Rvalue - copyable:\n";	auto rref_copy_rf = with_forward<byRvalueReference>(copyableMovable{});
   std::cout << "Rvalue - moveable:\n";	auto rref_move_rf = with_forward<byRvalueReference>(onlyMovable{});
   std::cout << "With forward - short summary:\nPassing by value is impossible for onlyMovable lvalue.\n"
		"Passing by non const lvalue reference does not work at all.\n"
		"Passing by const lvalue reference is impossible for onlyMovable lvalue.\n";
		
   std::cout << "\n\nSUMMARY\nAfter enabling forward in function rerturning unique_ptr to it's argument:\n"
		" - passing onlyMovable rvalue by value becomes possible\n"
		" - passing lvalue copyableMovable and all rvalues by non const lvalue reference becomes impossible\n"
		" - passing by const lvalue reference does not change\n"
		" - passing by rvalue reference becomes possible for both lvalues and rvalues of both copyableMovable and onlyMovable\n"
		"Experiment overlooks onlyCopyable type.\n";
}