#include <iostream>
#include <type_traits>

struct Data {
  Data()                 { std::cout << "  constructor\n";}
  Data(const Data& data) { std::cout << "  copy constructor\n";} 
  Data(Data&& data)      { std::cout << "  move constructor\n";}
};

struct DataWrapperWithMove {
  Data data_;
  DataWrapperWithMove(Data&& data) : data_(std::move(data)) { }
};

struct DataWrapperByValue {
  Data data_;
  DataWrapperByValue(Data data) : data_(std::move(data)) { }
};

struct DataWrapperByMoveOrCopy {
  Data data_;
  template<typename T, 
  	typename = typename std::enable_if<
  		std::is_same<typename std::decay<T>::type, Data>::value
  	>::type
  >
  DataWrapperByMoveOrCopy(T&& data) : data_(std::forward<T>(data)) { }
};

struct Foo {
  Foo()                 { std::cout << "  constructor\n";}
  Foo(const Foo& data) { std::cout << "  copy constructor\n";} 
  Foo(Foo&& data)      { std::cout << "  move constructor\n";}
};

struct Bar {
  Bar()                 { std::cout << "  constructor\n";}
  Bar(const Bar& data) { std::cout << "  copy constructor\n";} 
  Bar(Bar&& data)      { std::cout << "  move constructor\n";}
};

struct Baz {
  Baz()                 { std::cout << "  constructor\n";}
  Baz(const Baz& data) { std::cout << "  copy constructor\n";} 
  Baz(Baz&& data)      { std::cout << "  move constructor\n";}
};

struct CompositeWrapperByMoveOrCopy {
  Data data_;
  Foo foo_;
  Bar bar_;
  Baz baz_;
  template<typename T, typename U, typename V, typename W, 
  	typename = typename std::enable_if<
  		std::is_same<typename std::decay<T>::type, Data>::value &&
  		std::is_same<typename std::decay<U>::type, Foo>::value &&
  		std::is_same<typename std::decay<V>::type, Bar>::value &&
  		std::is_same<typename std::decay<W>::type, Baz>::value
  	>::type
  >
  CompositeWrapperByMoveOrCopy(T&& data, U&& foo, V&& bar, W&& baz) : 
  data_{ std::forward<T>(data) },
  foo_{ std::forward<U>(foo) },
  bar_{ std::forward<V>(bar) },
  baz_{ std::forward<W>(baz) } { }
};

Data function_returning_data() {
  return Data();
}

int main() {
  std::cout << "1. DataWrapperWithMove:\n"; 
  Data d1;
  DataWrapperWithMove a1(std::move(d1));

  std::cout << "2. DataWrapperByValue:\n";  
  Data d2;
  DataWrapperByValue a2(std::move(d2));
  
  std::cout << "3. DataWrapperByMoveOrCopy:\n";  
  Data d3;
  DataWrapperByMoveOrCopy a3(std::move(d3));
  
  std::cout << "4. DataWrapperByMoveOrCopy with l-value:\n"; 
  Data d4;
  DataWrapperByMoveOrCopy a4(d4);

  std::cout << "5. RVO:\n";
  DataWrapperByValue a5(function_returning_data());
    
  std::cout << "6. CompositeWrapperByMoveOrCopy with l-value:\n"; 
  Data d6;
  Foo f;
  Bar b;
  Baz z;
  int i;
  CompositeWrapperByMoveOrCopy a6(d6, std::move(f), b, std::move(z)); //works
  //CompositeWrapperByMoveOrCopy a7(d6, std::move(f), z, std::move(b)); //SFINAE kills it
  //CompositeWrapperByMoveOrCopy a8(d6, std::move(f), b, i); //SFINAE kills it
}