How do I expand a tuple into variadic template function's arguments?

前端 未结 13 1008
旧时难觅i
旧时难觅i 2020-11-22 07:49

Consider the case of a templated function with variadic template arguments:

template Tret func(const T&... t);


        
相关标签:
13条回答
  • 2020-11-22 08:19

    This simple solution works for me:

    template<typename... T>
    void unwrap_tuple(std::tuple<T...>* tp)
    {
        std::cout << "And here I have the tuple types, all " << sizeof...(T) << " of them" << std::endl;
    }
    
    int main()
    {
        using TupleType = std::tuple<int, float, std::string, void*>;
    
        unwrap_tuple((TupleType*)nullptr); // trick compiler into using template param deduction
    }
    
    0 讨论(0)
  • 2020-11-22 08:20

    In C++17 you can do this:

    std::apply(the_function, the_tuple);
    

    This already works in Clang++ 3.9, using std::experimental::apply.

    Responding to the comment saying that this won't work if the_function is templated, the following is a work-around:

    #include <tuple>
    
    template <typename T, typename U> void my_func(T &&t, U &&u) {}
    
    int main(int argc, char *argv[argc]) {
    
      std::tuple<int, float> my_tuple;
    
      std::apply([](auto &&... args) { my_func(args...); }, my_tuple);
    
      return 0;
    }
    

    This work around is a simplified solution to the general problem of passing overload sets and function template where a function would be expected. The general solution (one that is taking care of perfect-forwarding, constexpr-ness, and noexcept-ness) is presented here: https://blog.tartanllama.xyz/passing-overload-sets/.

    0 讨论(0)
  • 2020-11-22 08:20

    All this implementations are good. But due to use of pointer to member function compiler often cannot inline the target function call (at least gcc 4.8 can't, no matter what Why gcc can't inline function pointers that can be determined?)

    But things changes if send pointer to member function as template arguments, not as function params:

    /// from https://stackoverflow.com/a/9288547/1559666
    template<int ...> struct seq {};
    template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
    template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
    
    template<typename TT>
    using makeSeq = typename gens< std::tuple_size< typename std::decay<TT>::type >::value >::type;
    
    
    // deduce function return type
    template<class ...Args>
    struct fn_type;
    
    template<class ...Args>
    struct fn_type< std::tuple<Args...> >{
    
        // will not be called
        template<class Self, class Fn>
        static auto type_helper(Self &self, Fn f) -> decltype((self.*f)(declval<Args>()...)){
            //return (self.*f)(Args()...);
            return NULL;
        }
    };
    
    template<class Self, class ...Args>
    struct APPLY_TUPLE{};
    
    template<class Self, class ...Args>
    struct APPLY_TUPLE<Self, std::tuple<Args...>>{
        Self &self;
        APPLY_TUPLE(Self &self): self(self){}
    
        template<class T, T (Self::* f)(Args...),  class Tuple>
        void delayed_call(Tuple &&list){
            caller<T, f, Tuple >(forward<Tuple>(list), makeSeq<Tuple>() );
        }
    
        template<class T, T (Self::* f)(Args...), class Tuple, int ...S>
        void caller(Tuple &&list, const seq<S...>){
            (self.*f)( std::get<S>(forward<Tuple>(list))... );
        }
    };
    
    #define type_of(val) typename decay<decltype(val)>::type
    
    #define apply_tuple(obj, fname, tuple) \
        APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
                decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
                &decay<decltype(obj)>::type::fname \
                > \
                (tuple);
    

    And ussage:

    struct DelayedCall
    {  
        void call_me(int a, int b, int c){
            std::cout << a+b+c;
        }
    
        void fire(){
            tuple<int,int,int> list = make_tuple(1,2,3);
            apply_tuple(*this, call_me, list); // even simpler than previous implementations
        }
    };
    

    Proof of inlinable http://goo.gl/5UqVnC


    With small changes, we can "overload" apply_tuple:

    #define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
    #define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
    #define VARARG_IMPL_(base, count, ...) base##count(__VA_ARGS__)
    #define VARARG_IMPL(base, count, ...) VARARG_IMPL_(base, count, __VA_ARGS__)
    #define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)
    
    #define apply_tuple2(fname, tuple) apply_tuple3(*this, fname, tuple)
    #define apply_tuple3(obj, fname, tuple) \
        APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
                decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
                &decay<decltype(obj)>::type::fname \
                /* ,decltype(tuple) */> \
                (tuple);
    #define apply_tuple(...) VARARG(apply_tuple, __VA_ARGS__)
    
    ...
    
    apply_tuple(obj, call_me, list);
    apply_tuple(call_me, list);       // call this->call_me(list....)
    

    Plus this is the only one solution which works with templated functions.

    0 讨论(0)
  • 2020-11-22 08:23
    template<typename F, typename Tuple, std::size_t ... I>
    auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
        return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
    }
    template<typename F, typename Tuple>
    auto apply(F&& f, Tuple&& t) {
        using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
        return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
    }
    

    This is adapted from the C++14 draft using index_sequence. I might propose to have apply in a future standard (TS).

    0 讨论(0)
  • 2020-11-22 08:26

    1) if you have a readymade parameter_pack structure as function argument, you can just use std::tie like this:

    template <class... Args>
    void tie_func(std::tuple<Args...> t, Args&... args)
    {
     std::tie<Args...>(args...) = t;
    }
    
    int main()
    {
     std::tuple<int, double, std::string> t(2, 3.3, "abc");
    
     int i;
     double d;
     std::string s;
    
     tie_func(t, i, d, s);
    
     std::cout << i << " " << d << " " << s << std::endl;
    }
    

    2) if you don't have a readymade parampack arg, you'll have to unwind the tuple like this

    #include <tuple>
    #include <functional>
    #include <iostream>
    
    
    
    template<int N>
    struct apply_wrap {
        template<typename R, typename... TupleArgs, typename... UnpackedArgs>
        static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>& t, UnpackedArgs... args )
        {
            return apply_wrap<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... );
        }
    };
    
    
    template<>
    struct apply_wrap<0>
    {
        template<typename R, typename... TupleArgs, typename... UnpackedArgs>
        static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>&, UnpackedArgs... args )
        {
            return f( args... );
        }
    };
    
    
    
    template<typename R, typename... TupleArgs>
    R applyTuple( std::function<R(TupleArgs...)>& f, std::tuple<TupleArgs...> const& t )
    {
        return apply_wrap<sizeof...(TupleArgs)>::applyTuple( f, t );
    }
    
    
    
    int fac(int n)
    {
        int r=1;
        for(int i=2; i<=n; ++i)
            r *= i;
        return r;
    }
    
    
    
    int main()
    {
        auto t = std::make_tuple(5);
        auto f = std::function<decltype(fac)>(&fac);
        cout << applyTuple(f, t);
    }
    
    0 讨论(0)
  • 2020-11-22 08:26

    Extending on @David's solution, you can write a recursive template that

    1. Doesn't use the (overly-verbose, imo) integer_sequence semantics
    2. Doesn't use an extra temporary template parameter int N to count recursive iterations
    3. (Optional for static/global functors) uses the functor as a template parameter for compile-time optimizaion

    E.g.:

    template <class F, F func>
    struct static_functor {
        template <class... T, class... Args_tmp>
        static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
                -> decltype(func(std::declval<T>()...)) {
            return static_functor<F,func>::apply(t, args...,
                    std::get<sizeof...(Args_tmp)>(t));
        }
        template <class... T>
        static inline auto apply(const std::tuple<T...>& t, T... args)
                -> decltype(func(args...)) {
            return func(args...);
        }
    };
    
    static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);
    

    Alternatively if your functor is not defined at compile-time (e.g., a non-constexpr functor instance, or a lambda expression), you can use it as a function parameter instead of a class template parameter, and in fact remove the containing class entirely:

    template <class F, class... T, class... Args_tmp>
    inline auto apply_functor(F&& func, const std::tuple<T...>& t,
            Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
        return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
    }
    template <class F, class... T>
    inline auto apply_functor(F&& func, const std::tuple<T...>& t,
            T... args) -> decltype(func(args...)) {
        return func(args...);
    }
    
    apply_functor(&myFunc, my_tuple);
    

    For pointer-to-member-function callables, you can adjust either of the above code pieces similarly as in @David's answer.

    Explanation

    In reference to the second piece of code, there are two template functions: the first one takes the functor func, the tuple t with types T..., and a parameter pack args of types Args_tmp.... When called, it recursively adds the objects from t to the parameter pack one at a time, from beginning (0) to end, and calls the function again with the new incremented parameter pack.

    The second function's signature is almost identical to the first, except that it uses type T... for the parameter pack args. Thus, once args in the first function is completely filled with the values from t, it's type will be T... (in psuedo-code, typeid(T...) == typeid(Args_tmp...)), and thus the compiler will instead call the second overloaded function, which in turn calls func(args...).

    The code in the static functor example works identically, with the functor instead used as a class template argument.

    0 讨论(0)
提交回复
热议问题