#pragma once

#include <stdexcept>

using namespace std;


class NullIndirectError : public runtime_error{
public:
	NullIndirectError();
	NullIndirectError(const string &what_arg);
	NullIndirectError(const char *what_arg);
};


template <typename T>
class Indirect{
	T *ptr;

	class Dummy{};

	Indirect(Dummy);

public:
	template <typename ...Args>
	Indirect(Args... args);

	Indirect(const Indirect &other);
	Indirect(Indirect &&other);

	~Indirect();

	Indirect<T>& operator=(const T &value);
	Indirect<T>& operator=(T &&value);

	Indirect<T>& operator=(const Indirect<T> &other);
	Indirect<T>& operator=(Indirect<T> &&other);

	bool isEmpty() const;

	T& operator*();
	const T& operator*() const;

	static Indirect<T> makeEmpty();
};

template <typename T>
Indirect<T>::Indirect(Dummy)
	:ptr(nullptr){}

template <typename T>
Indirect<T> Indirect<T>::makeEmpty(){
	return Indirect<T>(Indirect::Dummy());
};


template <typename T>
template <typename ...Args>
Indirect<T>::Indirect(Args... args)
	:ptr(new T(args...)){}

template <typename T>
Indirect<T>::Indirect(const Indirect &other){
	if(!other.ptr){
		ptr=nullptr;
	} else {
		ptr=new T(*other.ptr);
	}
}

template <typename T>
Indirect<T>::Indirect(Indirect &&other){
	if(!other.ptr){
		ptr=nullptr;
	} else {
		ptr=new T(move(*other.ptr));
		other.ptr=nullptr;
	}
}

template <typename T>
Indirect<T>::~Indirect(){
	if(ptr){
		delete ptr;
	}
}


template <typename T>
Indirect<T>& Indirect<T>::operator=(const T &value){
	if(!ptr){
		ptr=new T(value);
	} else {
		*ptr=value;
	}
	return *this;
}

template <typename T>
Indirect<T>& Indirect<T>::operator=(T &&value){
	if(!ptr){
		ptr=new T(value);
	} else {
		*ptr=value;
	}
	return *this;
}


template <typename T>
Indirect<T>& Indirect<T>::operator=(const Indirect<T> &other){
	if(ptr){
		delete ptr;
	}
	if(!other.ptr){
		ptr=nullptr;
	} else {
		ptr=new T(*other.ptr);
	}
	return *this;
}

template <typename T>
Indirect<T>& Indirect<T>::operator=(Indirect<T> &&other){
	if(ptr){
		delete ptr;
	}
	if(!other.ptr){
		ptr=nullptr;
	} else {
		ptr=new T(move(*other.ptr));
		other.ptr=nullptr;
	}
	return *this;
}


template <typename T>
bool Indirect<T>::isEmpty() const {
	return ptr!=nullptr;
}

template <typename T>
T& Indirect<T>::operator*(){
	if(!ptr){
		throw NullIndirectError();
	}
	return *ptr;
}

template <typename T>
const T& Indirect<T>::operator*() const {
	if(!ptr){
		throw NullIndirectError();
	}
	return *ptr;
}