#include <iostream> // std::cout, std::boolalpha, std::endl
#include <string> // std::basic_string
#include <type_traits> // std::enable_if_t, std::is_same
#include <utility> // std::swap, std::move

namespace danger_zone // enter at your own peril
{
	template<typename CharT>
	struct nullable_basic_string : public std::basic_string<CharT>
	{
	protected:
		using basic_string = std::basic_string<CharT>;
		
		bool _isNull = false;
		
	public:
		using basic_string::basic_string; // inherit constructors
		
		nullable_basic_string(basic_string const &str) : basic_string{ str }
		{
		}
		
		nullable_basic_string(decltype(nullptr)) : basic_string{}, _isNull{ true }
		{
		}
		
		nullable_basic_string() : nullable_basic_string{ nullptr }
		{
		}
		
		nullable_basic_string& operator=(nullable_basic_string rhs)
		{
			using std::swap;
			basic_string::swap(rhs);
			swap(_isNull, rhs._isNull);
			return *this;
		}
		
		friend bool operator==(nullable_basic_string const &lhs, decltype(nullptr) const &rhs)
		{
			return lhs._isNull;
		}
		
		friend bool operator==(decltype(nullptr) const &lhs, nullable_basic_string const &rhs)
		{
			return rhs == lhs;
		}
		
		friend bool operator!=(nullable_basic_string const &lhs, decltype(nullptr) const &rhs)
		{
			return !(lhs == rhs);
		}
		
		friend bool operator!=(decltype(nullptr) const &lhs, nullable_basic_string const &rhs)
		{
			return rhs != lhs;
		}
	};
	
	using nullable_string = nullable_basic_string<char>;
	
	////////////////////////////////////////////////////
	// TODO: throw exception on null access           //
	////////////////////////////////////////////////////
	
} // namespace danger_zone

int main(int, char**) noexcept
{
	using danger_zone::nullable_string;
	nullable_string ns{};
	nullable_string ns_copy{ ns };
	nullable_string tmp_copy_n{ std::move(nullable_string{}) };
	nullable_string tmp_copy_h{ std::move(nullable_string{ "Hello World!" }) };
	nullable_string hello{ "Hello World!" };
	nullable_string ns_assigned{};
	ns_assigned = "Goodbye, Galaxy!";
	nullable_string hello_assigned{ hello };
	hello_assigned = nullptr;
	std::cout << std::boolalpha << std::endl
		<< "ns == nullptr? " << (ns == nullptr) << std::endl
		<< "nullptr == ns? " << (nullptr == ns) << std::endl
		<< "ns_copy == nullptr? " << (ns_copy == nullptr) << std::endl
		<< "tmp_copy_n == nullptr? " << (tmp_copy_n == nullptr) << std::endl
		<< "tmp_copy_h == nullptr? " << (tmp_copy_h == nullptr) << std::endl
		<< "hello == nullptr? " << (hello == nullptr) << std::endl
		<< "ns_assigned == nullptr? " << (ns_assigned == nullptr) << std::endl
		<< "hello_assigned == nullptr? " << (hello_assigned == nullptr) << std::endl
		<< ns_assigned << std::endl;
	return 0;
}