#include <typeinfo>
#include <vector>
#include <cassert>

//Quick macro to replace my real Assert() function, just for this file.
#define Assert(condition, message) assert(condition && message)

//Holds any type of user-given pointer that can be cast to void, and does run-time checks on the type of the data when retrieving it.
//Ownership is *not* taken. It is the original owner's responsibillity to ensure the lifetime of the object when
//someone tries to retrieve the pointer later. The Userdata class itself never de-references the pointer.
class Userdata
{
public:
    Userdata() = default;
	
	template<typename UserType>
	Userdata(UserType *data)
	{
		this->Set(data);
	}
	
	//Sets a user-spe
	template<typename UserType>
	void Set(UserType *data)
	{
		//Save the pointer (cast to void*), and the type's RTTI information, so we can validate on retrieval.
		this->type_info = &typeid(UserType);
		this->userdata = data;
	}
	
	template<typename UserType>
	UserType *Get()
	{
		//Validate that we've actually set *anything*.
		Assert(this->type_info, "We never set any data, so we can't retrieve any.");
		
		//Validate that we are getting the same type that we initially set.
		Assert((*this->type_info) == typeid(UserType), "The types don't match - we can't retrieve the userdata safely.");
		
		return static_cast<UserType*>(this->userdata);
	}
	
	void Reset()
	{
		this->type_info = nullptr;
		this->userdata = nullptr;
	}

private:
	//A pointer to the global compiler-specific (but standardized) type_info for this type (the type that Userdata's void* points to).
	//C++11 guarantees that "The lifetime of the object referred to by [typeid()'s return value] extends to the end of the program." (N3337 5.2.8)
	const std::type_info *type_info = nullptr; 
	
	//A pointer to the user-defined data.
	void *userdata = nullptr;
};

//Manages a FILO stack of void pointers in a type-safe way.
//This class does not take any ownership of the data pointed to.
class UserdataStack
{
public:
	UserdataStack() = default;
	~UserdataStack() = default;
	
	template<typename UserType>
	void Push(UserType *userdata)
	{
		this->stack.push_back(Userdata(userdata));
	}
	
	template<typename UserType>
	UserType *Get()
	{
		Assert(!this->stack.empty(), "The stack is empty - we can't retrieve any userdata.");
		return this->stack.back().Get<UserType>();
	}
	
	void Pop()
	{
		if(!this->stack.empty())
		{
			this->stack.pop_back();
		}
	}
    
    //Pops the stack, and returns the pointer previously pointed to.
    template<typename UserType>
	UserType *Take()
	{
		UserType *data = this->Get<UserType>();
		this->Pop();
		
		return data;
	}
	
	void Clear()
	{
		this->stack.clear();
	}
	
private:
	std::vector<Userdata> stack;
};

//=====================================================

#include <string>
#include <iostream>

int main()
{
    int myInt = 357;
    float myFloat = 7.53f;
    std::string myString = "Test";
    
    UserdataStack stack;
    stack.Push(&myInt);
    stack.Push(&myFloat);
    stack.Push(&myString);
    
    //Asserts, because it's the wrong type.
    //int *test = stack.Get<int>();
    
    std::cout << "std::string:\t" << *(stack.Take<std::string>()) << " (should be " << myString << ")" << std::endl;
    std::cout << "float:\t\t"     << *(stack.Take<float>())       << " (should be " << myFloat  << ")" << std::endl;
    std::cout << "int:\t\t"         << *(stack.Take<int>())         << " (should be " << myInt    << ")" << std::endl;
    
    //Asserts, because the stack is now empty:
    //stack.Take<int>();
    
    return 0;
}
