#include <iostream>
#include <type_traits>
#include <functional>


template <typename T>
using GetBaseType = typename T::Base;
using NoBase = void;

template <typename T>
class CallUp;

template <>
class CallUp<NoBase>
{
public:
   template <typename Op, typename T>
   CallUp(Op&& ignoreOperation, T&& ignoreObject) {}
};


template <typename T>
class CallUp : public CallUp<GetBaseType<T>>
{
public:
 	template <typename Op>
	CallUp(Op&& op, T& obj) : CallUp<GetBaseType<T>>(op, obj), op(std::forward<Op>(op)), obj(obj)
	{}
	~CallUp()
	{
		op(obj);
	}
private:
	std::function<void(T&)> op;
	T& obj;
};

template <typename T>
class ConstCallUp;

template <>
class ConstCallUp<NoBase>
{
public:
   template <typename Op, typename T>
   ConstCallUp(Op&& ignoreOperation, T&& ignoreObject) {}
};

template <typename T>
class ConstCallUp : public ConstCallUp<GetBaseType<T>>
{
public:
 	template <typename Op>
	ConstCallUp(Op&& op, const T& obj) : ConstCallUp<GetBaseType<T>>(op, obj), op(std::forward<Op>(op)), obj(obj)
	{}
	~ConstCallUp()
	{
		op(obj);
	}
private:
	std::function<void(T&)> op;
	const T& obj;
};

template <typename Op, typename T>
auto callUp(Op&& op, T& obj)
{
	return CallUp<T>(std::forward<Op>(op), obj);
}
template <typename Op, typename T>
auto callUp(Op&& op, const T& obj)
{
	return ConstCallUp<T>(std::forward<Op>(op), obj);
}

class Foo0
{
public:
	using Base = NoBase;
	void reset()
	{
		std::cout << "reset Foo0\n";
	}
	void print() const
	{
		std::cout << "Foo0\n";
	}
};

class Foo1 : public Foo0
{
public:
	using Base = Foo0;
	void reset()
	{
		std::cout << "reset Foo1\n";
	}
	void print() const
	{
		std::cout << "Foo1\n";
	}
};

class Foo2 : public Foo1
{
public:
	using Base = Foo1;
	void reset()
	{
		std::cout << "reset Foo2\n";
	}
	void print() const
	{
		std::cout << "Foo2\n";
	}
};


int main() {
	Foo2 foo2;
	callUp([](auto&& obj){obj.reset();}, foo2);
	callUp([](auto&& obj){obj.print();}, foo2);
}