Convert type information from run-time to compile-time



  • Can you explain what you do there:

    template <typename firstType, typename... types, typename... Args>
    

    ?
    How can I understand this using of 2 variadic templates?



  • Mr.Long schrieb:

    Can you explain what you do there:

    template <typename firstType, typename... types, typename... Args>
    

    ?
    How can I understand this using of 2 variadic templates?

    And how can a compiler distinguish when which parameter pack ends/starts?
    This might be an error, isnt it?



  • Skym0sh0 schrieb:

    Mr.Long schrieb:

    Can you explain what you do there:

    template <typename firstType, typename... types, typename... Args>
    

    ?
    How can I understand this using of 2 variadic templates?

    And how can a compiler distinguish when which parameter pack ends/starts?
    This might be an error, isnt it?

    No, it is not an error. It is a common pattern when dealing with classes with variadic template arguments.
    The first variadic template argument (types) belongs to the type_list argument, the second one (Args) to normal function arguments. This allows callWithType to get any type_list where the first type is firstType followed by an arbitrary number of arguments.
    It is no problem for the compiler, since one argument is a template parameter and the other one normal function arguments. Similar to e.g. std::tuple comparision operators: http://en.cppreference.com/w/cpp/utility/tuple/operator_cmp


  • Mod

    Skym0sh0 schrieb:

    Mr.Long schrieb:

    Can you explain what you do there:

    template <typename firstType, typename... types, typename... Args>
    

    ?
    How can I understand this using of 2 variadic templates?

    And how can a compiler distinguish when which parameter pack ends/starts?
    This might be an error, isnt it?

    In a primary class template if there is a template parameter pack, it must be the last parameter of that template.

    Similarly, in a function template if there is a template parameter pack that cannot be deduced from its function parameters, it must be the last template parameter. If a function template has a function parameter that is a pack expansion it must be the last function parameter.



  • Thank you, Arcoth! Your solution works (except I needed to update my GCC for it to compile), but the problem is that I have additional degree of freedom: the name of the called templated function. The code you presented has f1 function name hardcoded, but the same procedure should be somehow performed for functions f1 ... f9 , not just for f1 .

    Unfortunately, I found no C++ way to pass name of function template into any other class of function. Suppose you have:

    template<class T> T f(void) { return T(); }
    

    Then it looks like you have no way to pass f either as template (or template template) parameter, or as function argument.

    If you try to implement f1 ... f9 as functors, then I suppose you will have no way to make tables of such functors or their members since they will have different types.

    Below is my variant of your code (simplified and adapted for my needs), but it does not compile. May be the best way to fix it is to encode the name of the function to call as third integer template parameter and use third (innermost) dispatch function?

    template <class, int> struct Vec {};
    //Vec is not my class: it takes «int» instead of «size_t». So «int» is used everywhere.
    
    template<class T> void f1(int a1) {
    	std::cout << "f1 with " << a1 << " called, " << __PRETTY_FUNCTION__ << std::endl;
    }
    
    template<class T> void f2(double a1, int a2) {
    	std::cout << "f2 with " << a1 << ", " << a2 << " called, " << __PRETTY_FUNCTION__ << std::endl;
    }
    
    template <class F, class T, class... Args>
    decltype(auto) callWithIndex(int const n, Args &&... args) {
    	static constexpr decltype(F< Vec<T, 1> >)* table[] = { //ERROR: «F is not a template»
    		F< Vec<T, 1> >, //F< Vec<T, 0> > is useless, so I skipped it
    		F< Vec<T, 2> >,
    		F< Vec<T, 3> >,
    		F< Vec<T, 4> >
    	};
    	return table[n - 1](std::forward<Args>(args)...);
    }
    
    template <class F, class... Args>
    decltype(auto) callWithTypeAndIndex(int const tid, int const n, Args &&... args) {
    	static constexpr decltype(callWithIndex<unsigned char, Args...>)* table[] = {
    		callWithIndex<F, unsigned char , Args...>,
    		callWithIndex<F,   signed char , Args...>,
    		callWithIndex<F, unsigned short, Args...>,
    		callWithIndex<F,   signed short, Args...>,
    		callWithIndex<F,            int, Args...>,
    		callWithIndex<F,          float, Args...>,
    		callWithIndex<F,         double, Args...>
    	};
    	return table[tid](n, std::forward<Args>(args)...);
    }
    
    void f1(int tid, int n, int a1) {
    	callWithTypeAndIndex<f1>(tid, n, a1);
    }
    
    void f2(int tid, int n, double a1, int a2) {
    	callWithTypeAndIndex<f2>(tid, n, a1, a2);
    }
    


  • Offtopic: I have no such problems in Julia since JIT-compiler is always ready to compile some additional template function instantiations for me.


  • Mod

    Sorry for taking that long to reply. What about sth. along the lines of

    #include <iostream>
    #include <utility>
    
    template <class, int> struct Vec {};
    //Vec is not my class: it takes «int» instead of «size_t». So «int» is used everywhere.
    
    template<class T> void f1(int a1) {
        std::cout << "f1 with " << a1 << " called, " << __PRETTY_FUNCTION__ << std::endl;
    }
    
    template<class T> void f2(double a1, int a2) {
        std::cout << "f2 with " << a1 << ", " << a2 << " called, " << __PRETTY_FUNCTION__ << std::endl;
    }
    
    template <template <class...> class F, class T, class... Args>
    decltype(auto) callWithIndex(int const n, Args &&... args) {
        static constexpr decltype(&F< Vec<T, 1> >::call) table[] = {
            &F< Vec<T, 1> >::call,
            &F< Vec<T, 2> >::call,
            &F< Vec<T, 3> >::call,
            &F< Vec<T, 4> >::call
        };
        return table[n - 1](std::forward<Args>(args)...);
    }
    
    template <template <class...> class F, class... Args>
    decltype(auto) callWithTypeAndIndex(int const tid, int const n, Args &&... args) {
        static constexpr decltype(callWithIndex<F, unsigned char, Args...>)* table[] = {
            callWithIndex<F, unsigned char , Args...>,
            callWithIndex<F,   signed char , Args...>,
            callWithIndex<F, unsigned short, Args...>,
            callWithIndex<F,   signed short, Args...>,
            callWithIndex<F,            int, Args...>,
            callWithIndex<F,          float, Args...>,
            callWithIndex<F,         double, Args...>
        };
        return table[tid](n, std::forward<Args>(args)...);
    }
    
    template <typename T>
    struct f1_template
    { static void call(int a1) {f1<T>(a1);} };
    
    template <typename T>
    struct f2_template
    { static void call(double a1, int a2) {f2<T>(a1, a2);} };
    
    void f1(int tid, int n, int a1) {
        callWithTypeAndIndex<f1_template>(tid, n, a1);
    }
    
    void f2(int tid, int n, double a1, int a2) {
        callWithTypeAndIndex<f2_template>(tid, n, a1, a2);
    }
    
    int main()
    {
    	f1(1, 2, 3);
    	f1(2, 0, 3);
    }
    

    Demo.

    That also works with my first approach. See here.

    You could perhaps also use a macro that deduces the parameter and return types so you don't have to type out the parameter list every time.



  • Hi Arcoth!

    I am currently investigating more «universal» dispatch method based on your suggestions, and here is what I got:

    template<template<class> class F, class A, class ...B> struct Dispatch {
    	template<class ...C> static inline void f(std::size_t const i, C &&...c) {
    		assert( i <= sizeof...(B) );
    		static constexpr decltype(&F<A>::f) table[]{&F<A>::f, &F<B>::f...};
    		table[i](std::forward<C>(c)...);
    	}
    };
    

    I can use this this way:

    template<class A> struct Test1 {
    	static void f(int b) {
    		std::cout << typeid(A).name() << ", " << b << std::endl;
    	}
    };
    
    #define LIST int, float, double
    
    int main(void) {
    	Dispatch<Test1, LIST>::f(2, 3);
    	return 0;
    }
    

    The problem is that I cannot figure out how to make dispatching by more than one variable.

    Ideally I want something like that to compile:

    template<class A, class B> struct Test2 {
    	static void f(int c) {
    		std::cout << typeid(A).name() << ", " << typeid(B).name() << ", " << c << std::endl;
    	}
    }
    
    int main(void) {
    	Dispatch<Dispatch<Test2, LIST>, LIST>::f(1, 2, 3);
    	return 0;
    };
    


  • May be other form of dynamic dispatch such as virtual functions can be utilzed for this task?



  • I have done it!

    I have tried to do it for 3 months! Then I given up and wrote here that I cannot solve the problem. While I was writing something changed in my mind, and I came up to the solution in 2 hours. Here it is: universal runtime-to-compile-time dispatcher:

    template<class F, class A, class ...B> struct Dispatch {
    	template<class ...C> static inline decltype(&F::template f<C..., A>) f(std::size_t const i) {
    		assert( i <= sizeof...(B) );
    		static constexpr decltype(&F::template f<C..., A>) table[] {
    			&F::template f<C..., A>, &F::template f<C..., B>...
    		};
    		return table[i];
    	}
    };
    
    struct Test1 {
    	template<class A> static void f(int b) {
    		std::cout << typeid(A).name() << ", " << b << std::endl;
    	}
    };
    
    struct Test2 {
    	template<class A, class B> static void f(int c) {
    		std::cout << typeid(A).name() << ", " << typeid(B).name() << ", " << c << std::endl;
    	}
    };
    
    #define LIST int, float, double
    
    int main(void) {
    
    	Dispatch<Test1, LIST>::f(1)(2); //«float, 2»
    
    	Dispatch<Dispatch<Test2, LIST>, LIST>::f(1)(2)(3); //«float, double, 3»
    
    	return 0;
    }
    

    Thanks!


Anmelden zum Antworten