#include <iostream>
#include <type_traits>

    namespace ParameterCheck {
        namespace ParamGetter {
        	// Based on an answer from GManNickG ( http://stackoverflow.com/a/4693493/5386374 )
        	
            // Turn the type list into a single type we can use with std::is_same.
            template<typename... Ts> struct variadic_typedef { };

            // Generic case, to catch passed parameter types list.
            template<typename... Ts> struct variadic_wrapper {
            	using type = variadic_typedef<Ts...>;
            };
  
            // Special case to catch void parameter types list.
            template<> struct variadic_wrapper<> {
            	using type = variadic_typedef<void>;
            };

            // Generic case to isolate parameter list from function signature.
            template<typename RT, typename... Ts> struct variadic_wrapper<RT (*)(Ts...)> {
            	using type = variadic_typedef<Ts...>;
            };
            
            // Special case to isolate void parameter from function signature.
            template<typename RT> struct variadic_wrapper<RT (*)()> {
            	using type = variadic_typedef<void>;
            };

/*
            template<typename... Ts> struct convert_in_tuple {
                using type = std::tuple<Ts...>;
            };

            template<typename... Ts> struct convert_in_tuple<variadic_typedef<Ts...> > {
                using type = typename convert_in_tuple<Ts...>::type;
            };
*/
        } // namespace ParamGetter
        
        template<typename... Ts> using PGetter = typename ParamGetter::variadic_wrapper<Ts...>::type;

//        template<typename... Ts> using PGetter = typename ParamGetter::convert_in_tuple<ParamGetter::variadic_typedef<Ts...> >::type;

        // Declare class template.
        template<typename... Ts> struct parameter_match;

        // Actual class.  Becomes either std::true_type or std::false_type.
        template<typename F, typename... Ts> struct parameter_match<F, Ts...> : public std::integral_constant<bool, std::is_same<PGetter<F>, PGetter<Ts...> >{}> {};
    } // namespace ParameterCheck
    
    template<typename F, typename... Ts> using PChecker = ParameterCheck::parameter_match<F, Ts...>;
    // template<typename F, typename... Ts> using PChecker = ParameterCheck::parameter_match<F, Ts&...>;
    
// -------------------------

// Looking for a way to just grab the parameter list right out of the passed function, feel free to ignore this section for now.
namespace ParameterListCapture {
	template<typename T> struct p_list;
	
	template<typename RT> struct p_list<RT (*)()> { };
	
	/*
	// Doesn't work, sadly.
	template<typename RT, typename... Ts> struct p_list<RT (*)(Ts...)> {
		using type = Ts...;
	};
	*/

} // namespace ParameterListCapture

// -------------------------

    // The actual calling function.
template<typename Func, typename... Ts> auto caller2(std::true_type x, Func f, Ts... args) {
    std::cout << "Now calling... ";
    return f(args...);
}

// Parameter mismatch overload.
template<typename Func, typename... Ts> auto caller2(std::false_type x, Func f, Ts... args) {
    std::cout << "Parameter list mismatch." << std::endl;
}

// Wrapper to check for parameter mismatch.
template<typename Func, typename... Ts> auto caller(Func f, Ts... args) {
    // return caller2(ParameterCheck::parameter_match<Func, Ts...>{}, f, args...);
    return caller2(PChecker<Func, Ts...>{}, f, args...);
}
    
// -------------------------

void f1() { std::cout << "Hi." << std::endl; }
int f2(int a, int b, int* c) {
	std::cout << a + b + *c << std::endl;
	return b - a;
}
void f3(const int a, const bool b, bool c) { std::cout << "Arguments?  What arguments?" << std::endl; }
bool f4(int& a, const float b) { std::cout << (b > a); return (a > b); }
void f5(const char* a) { std::cout << a << std::endl; }

void f0(int& a) {
	std::cout << "f5(): Value is: " << a++ << std::endl;
	std::cout << "Incremented, returning." << std::endl;
}

void f01(int a) { std::cout << "Let's see what this does." << std::endl; }


template<typename Func, typename... Ts> void checker(Func f, Ts... args) {
	std::cout << PChecker<Func, Ts...>{} << std::endl ;
	caller(f, args...);
}

template<typename Func, typename T> void passThrough2(Func f, T t) {
// template<typename Func, typename T> void passThrough2(Func f, T& t) { // Makes f0() work properly, but still not pass checker().
	f(t);
	std::cout << "passThrough2(): Value is now: " << t << std::endl;
}

template<typename Func, typename... Ts> void passThrough(Func f, Ts... args) {
// template<typename Func, typename... Ts> void passThrough(Func f, Ts&... args) { // Makes f0() work properly, but still not pass checker().
	std::cout << "Passing through passThrough()..." << std::endl;
	passThrough2(f, args...);
}

int main() {
	int a = -17, x = 3;
	int refA = a;
	const float b = 1.2f;
	bool c = false;
	const char* d = "Hihihi.\n";
	char* e = const_cast<char*>(d);
	std::cout << std::boolalpha;
	
	std::cout << "Empty parameter list testing:" << std::endl;
	std::cout << "Testing (void(*)(), void): ";
	checker(f1);
	
	std::cout << "Testing (void(*)(), int): ";
	checker(f1, 3);
	
	std::cout << "Testing (void(*)(), void*): ";
	checker(f1, (void*) nullptr);
	
	std::cout << " - - - -" << std::endl;
	std::cout << "Standard parameters (including pointers) testing:" << std::endl;
	std::cout << "Testing (int(*)(int, int, int*), int, int, int*): ";
	checker(f2, 3, 9, &a);
	
	std::cout << "Testing (int(*)(int, int, int*), int*, int, int): ";
	checker(f2, &a, 3, 9);

	std::cout << " - - - -" << std::endl;
	std::cout << "Const testing:" << std::endl;
	std::cout << "Testing (void(*)(const int, const bool, bool), int, bool [literal], bool [literal]): ";
	checker(f3, a, true, false);
	
	std::cout << "Testing (void(*)(const int, const bool, bool), int [literal], bool, bool): ";
	checker(f3, 4, c, c);
	
	std::cout << "Testing (void(*)(const char*), const char*): ";
	checker(f5, d);
	
	std::cout << "Testing (void(*)(const char*), char*): ";
	checker(f5, e);
	std::cout << "Adds const-ness to primitives during function call, but not to pointers." << std::endl;

	std::cout << " - - - -" << std::endl;
    std::cout << "Reference testing:" << std::endl;	
	std::cout << "Testing (bool(*)(int&, const float), int&, const float): ";
	checker(f4, a, b);
	
	std::cout << "Testing (bool(*)(int&, const float), [explicit] int&, float): ";
	checker(f4, refA, b);
	
	std::cout << "Testing (bool(*)(int&, float), int&, int): ";
	checker(f4, a, a);
	std::cout << "Still some issues." << std::endl;
	
	std::cout << " - - - -" << std::endl;
	std::cout << "Testing passing reference through variadic template:" << std::endl;
	std::cout << "Variable 'x' original value: " << x << std::endl;
	passThrough(f0, x);
	std::cout << "And finally, back in main(): 'x' new value: " << x << std::endl;

	std::cout << "Let's try it straight into passThrough2(): " << std::endl;
	std::cout << "x starting value: " << x << std::endl;
	passThrough2(f0, x);
	std::cout << "x new value: " << x << std::endl;
	
	std::cout << "And now, one last thing, 1) passing (int) to template func expecting (int&):" << std::endl;
	passThrough(f01, x);
	
	std::cout << "And 2) Checking function signatures:" << std::endl;
	std::cout << "Comment out the above checker() calls, and change \"Ts...\" to \"Ts&...\" in checker()." << std::endl;
	std::cout << "Also, use the second version of PChecker (currently commented out), instead of the first." << std::endl;
	std::cout << "Note that if we explicitly specify that a parameter [pack] is a reference(s) like this, PChecker matching to non-reference types will fail." << std::endl;
	std::cout << "Testing for (void(*)(int&), int&): ";
	checker(f0, x);
	std::cout << "Testing for (void(*)(int), int&): ";
	checker(f1, x);
	
	
}