#include <iostream>
#include <tuple>
#include <type_traits>
using namespace std;


template<typename Derived>
class BaseData {
public:
    template<typename Visitor>
    void accept(Visitor &v){
         static_cast<Derived*>(this)->accept(v);
    }
};

class DerivedBaseData1: BaseData<DerivedBaseData1> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
    	std::cout << "DerivedBaseData1: accepting visitor " << v << std::endl;
    }    
};
class DerivedBaseData2: BaseData<DerivedBaseData2> {
public:
    template<typename Visitor>
    void accept(Visitor &v){
    	std::cout << "DerivedBaseData2: accepting visitor " << v << std::endl;
    }    
};

namespace impl {
		
	template <size_t N> 
	struct num2type {};
	
	template <size_t Idx, typename T, typename Visitor>
	void accept_impl(Visitor &v, T &&collection, num2type<Idx>) {
		// run accept on current object
		auto &object = std::get<Idx>(collection);
		object.accept(v);
		// move iteration forward
		accept_impl(v, std::forward<T>(collection), num2type<Idx - 1>{});
	}
	
	template <typename T, typename Visitor>
	void accept_impl(Visitor &v, T &&collection, num2type<0>) {
		// run accept on current object
		auto &object = std::get<0>(collection);
		object.accept(v);
	}
}

template<typename ...Ts, typename Visitor>
void accept(Visitor &v, std::tuple<Ts...> &&collection) {
	using T = decltype(collection);
	impl::accept_impl(v, std::forward<T>(collection), impl::num2type<std::tuple_size<std::decay_t<T>>::value - 1>{});
}


int main() {
	using visitor_type = int;
	visitor_type visitor = 42;

	DerivedBaseData1 a1, a3;
	DerivedBaseData2 a2;
	accept(visitor, std::tie(a1, a2, a3));
	
	return 0;
}