#include <iostream>
#include <memory>
#include <typeindex>

class any_ptr
{
    struct WrapperBase
    {
        virtual ~WrapperBase(){}
    };

    template< typename T >
    struct Wrapper : WrapperBase, T
    {
        template<typename ... Args>
        Wrapper( Args&&... args ):
            T( std::forward<Args>(args)... ) {}
    };

    struct PointerBase : WrapperBase
    {
		std::type_index typeIndex;

		PointerBase( std::type_index ti ):
			typeIndex{ti} {}
    };

    template< typename T >
    struct ScalarWrapper : WrapperBase
    {
    	T scalar;

    	template<typename Arg>
    	ScalarWrapper( Arg&& arg ):
    		scalar( std::forward<Arg>(arg) ) {}
    };

    std::unique_ptr<WrapperBase> mPtr;

    any_ptr( std::unique_ptr<WrapperBase> ptr ):
        mPtr{ std::move(ptr) } {}

    template< typename T >
    T* _access( std::false_type, std::false_type ) const
    {
        return dynamic_cast<T*>(mPtr.get());
    }

    template< typename T >
    T* _access( std::true_type, std::false_type ) const
    {
    	auto ptr = dynamic_cast<ScalarWrapper<T>*>(mPtr.get());
        return ptr ? &ptr->scalar : nullptr ;
    }

    template< typename T >
    T* _access( std::true_type, std::true_type ) const
    {
    	using pointee = typename std::remove_pointer<T>::type;

    	auto ptr = dynamic_cast<PointerBase*>(mPtr.get());

    	if( ptr
		 && ptr->typeIndex <= typeid(pointee) ) // Dieser Typ ist eine Basisklasse oder ein größerer Skalar o.ä.
			return &static_cast<ScalarWrapper<T>*>(dynamic_cast<void*>(ptr))->scalar;

    	return _access<T>( std::true_type{}, std::false_type{} );
    }

    template< typename T,
              typename ... Args >
    static any_ptr _make_any_ptr( std::false_type, Args&&... args )
    {
        return std::unique_ptr<WrapperBase>{ new Wrapper<T>{ std::forward<Args>(args)... } };
    }

    template< typename T >
    static any_ptr _make_any_ptr( std::true_type, T arg )
    {
        return std::unique_ptr<WrapperBase>{ new ScalarWrapper<T>{arg} };
    }

public:

	template< typename T >
	T* access()
	{ return _access<T>( typename std::is_scalar<T>::type{}, typename std::is_pointer<T>::type{} ); }

	template< typename T >
	T const* access() const
	{ return const_cast<any_ptr*>(this)->access<T>(); }

    template< typename T,
              typename ... Args >
    friend any_ptr make_any_ptr( Args&&... args )
    {
        return _make_any_ptr<T>( typename std::is_scalar<T>::type{}, std::forward<Args>(args)... );
    }
};

template< typename T >
struct any_ptr::ScalarWrapper<T*> : any_ptr::PointerBase
{
	T* scalar;

	template<typename Arg>
	ScalarWrapper( Arg&& arg ):
		PointerBase{ typeid(T) },
		scalar( std::forward<Arg>(arg) ) {}
};

////////////////////////////////////////////////////////////////////////////////

template <class T>
void inspect(any_ptr& p, void (&fun)(T))
{
    using actual_t = typename std::remove_reference<T>::type;

    if( auto t = p.access<actual_t>() )
        fun(*t);
}

////////////////////////////////////////////////////////////////////////////////

class printable
{
public:
    virtual void print() const = 0;
};

////////////////////////////////////////////////////////////////////////////////

#include <string>
using namespace std;

class foo : public printable
{
public:
    foo(string const& s) : m_s(s) {}

    virtual void print() const
    {
        cout << "foo: " << m_s << "\n";
    }

private:
    string m_s;
};

class bar : public printable
{
public:
    bar(int i) : m_i(i) {}

    virtual void print() const
    {
        cout << "bar: " << m_i << "\n";
    }

private:
    int m_i;
};

////////////////////////////////////////////////////////////////////////////////

void print_printable(printable& p)
{
    p.print();
}

////////////////////////////////////////////////////////////////////////////////

int main()
{
	bar b = 42;
	auto any = make_any_ptr<bar*>(&b);

	auto b_ptr = any.access<printable*>();

	if( b_ptr )
		(*b_ptr)->print();

	short i = 42;
	auto any2 = make_any_ptr<short*>(&i);

	auto i_ptr = any2.access<int*>();

	if( i_ptr )
		std::cout << **i_ptr;
}
