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

前端 未结 13 1010
旧时难觅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:28

    I find this to be the most elegant solution (and it is optimally forwarded):

    #include <cstddef>
    #include <tuple>
    #include <type_traits>
    #include <utility>
    
    template<size_t N>
    struct Apply {
        template<typename F, typename T, typename... A>
        static inline auto apply(F && f, T && t, A &&... a)
            -> decltype(Apply<N-1>::apply(
                ::std::forward<F>(f), ::std::forward<T>(t),
                ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
            ))
        {
            return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
                ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
            );
        }
    };
    
    template<>
    struct Apply<0> {
        template<typename F, typename T, typename... A>
        static inline auto apply(F && f, T &&, A &&... a)
            -> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
        {
            return ::std::forward<F>(f)(::std::forward<A>(a)...);
        }
    };
    
    template<typename F, typename T>
    inline auto apply(F && f, T && t)
        -> decltype(Apply< ::std::tuple_size<
            typename ::std::decay<T>::type
        >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
    {
        return Apply< ::std::tuple_size<
            typename ::std::decay<T>::type
        >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
    }
    

    Example usage:

    void foo(int i, bool b);
    
    std::tuple<int, bool> t = make_tuple(20, false);
    
    void m()
    {
        apply(&foo, t);
    }
    

    Unfortunately GCC (4.6 at least) fails to compile this with "sorry, unimplemented: mangling overload" (which simply means that the compiler doesn't yet fully implement the C++11 spec), and since it uses variadic templates, it wont work in MSVC, so it is more or less useless. However, once there is a compiler that supports the spec, it will be the best approach IMHO. (Note: it isn't that hard to modify this so that you can work around the deficiencies in GCC, or to implement it with Boost Preprocessor, but it ruins the elegance, so this is the version I am posting.)

    GCC 4.7 now supports this code just fine.

    Edit: Added forward around actual function call to support rvalue reference form *this in case you are using clang (or if anybody else actually gets around to adding it).

    Edit: Added missing forward around the function object in the non-member apply function's body. Thanks to pheedbaq for pointing out that it was missing.

    Edit: And here is the C++14 version just since it is so much nicer (doesn't actually compile yet):

    #include <cstddef>
    #include <tuple>
    #include <type_traits>
    #include <utility>
    
    template<size_t N>
    struct Apply {
        template<typename F, typename T, typename... A>
        static inline auto apply(F && f, T && t, A &&... a) {
            return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
                ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
            );
        }
    };
    
    template<>
    struct Apply<0> {
        template<typename F, typename T, typename... A>
        static inline auto apply(F && f, T &&, A &&... a) {
            return ::std::forward<F>(f)(::std::forward<A>(a)...);
        }
    };
    
    template<typename F, typename T>
    inline auto apply(F && f, T && t) {
        return Apply< ::std::tuple_size< ::std::decay_t<T>
          >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
    }
    

    Here is a version for member functions (not tested very much!):

    using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.
    
    template<size_t N>
    struct ApplyMember
    {
        template<typename C, typename F, typename T, typename... A>
        static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
            decltype(ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...))
        {
            return ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...);
        }
    };
    
    template<>
    struct ApplyMember<0>
    {
        template<typename C, typename F, typename T, typename... A>
        static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
            decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
        {
            return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
        }
    };
    
    // C is the class, F is the member function, T is the tuple.
    template<typename C, typename F, typename T>
    inline auto apply(C&& c, F&& f, T&& t) ->
        decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
    {
        return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
    }
    
    // Example:
    
    class MyClass
    {
    public:
        void foo(int i, bool b);
    };
    
    MyClass mc;
    
    std::tuple<int, bool> t = make_tuple(20, false);
    
    void m()
    {
        apply(&mc, &MyClass::foo, t);
    }
    
    0 讨论(0)
  • 2020-11-22 08:29

    The news does not look good.

    Having read over the just-released draft standard, I'm not seeing a built-in solution to this, which does seem odd.

    The best place to ask about such things (if you haven't already) is comp.lang.c++.moderated, because some folks involved in drafting the standard post there regularly.

    If you check out this thread, someone has the same question (maybe it's you, in which case you're going to find this whole answer a little frustrating!), and a few butt-ugly implementations are suggested.

    I just wondered if it would be simpler to make the function accept a tuple, as the conversion that way is easier. But this implies that all functions should accept tuples as arguments, for maximum flexibility, and so that just demonstrates the strangeness of not providing a built-in expansion of tuple to function argument pack.

    Update: the link above doesn't work - try pasting this:

    http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661

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

    Here's my code if anyone is interested

    Basically at compile time the compiler will recursively unroll all arguments in various inclusive function calls <N> -> calls <N-1> -> calls ... -> calls <0> which is the last one and the compiler will optimize away the various intermediate function calls to only keep the last one which is the equivalent of func(arg1, arg2, arg3, ...)

    Provided are 2 versions, one for a function called on an object and the other for a static function.

    #include <tr1/tuple>
    
    /**
     * Object Function Tuple Argument Unpacking
     *
     * This recursive template unpacks the tuple parameters into
     * variadic template arguments until we reach the count of 0 where the function
     * is called with the correct parameters
     *
     * @tparam N Number of tuple arguments to unroll
     *
     * @ingroup g_util_tuple
     */
    template < uint N >
    struct apply_obj_func
    {
      template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
      static void applyTuple( T* pObj,
                              void (T::*f)( ArgsF... ),
                              const std::tr1::tuple<ArgsT...>& t,
                              Args... args )
      {
        apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
      }
    };
    
    //-----------------------------------------------------------------------------
    
    /**
     * Object Function Tuple Argument Unpacking End Point
     *
     * This recursive template unpacks the tuple parameters into
     * variadic template arguments until we reach the count of 0 where the function
     * is called with the correct parameters
     *
     * @ingroup g_util_tuple
     */
    template <>
    struct apply_obj_func<0>
    {
      template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
      static void applyTuple( T* pObj,
                              void (T::*f)( ArgsF... ),
                              const std::tr1::tuple<ArgsT...>& /* t */,
                              Args... args )
      {
        (pObj->*f)( args... );
      }
    };
    
    //-----------------------------------------------------------------------------
    
    /**
     * Object Function Call Forwarding Using Tuple Pack Parameters
     */
    // Actual apply function
    template < typename T, typename... ArgsF, typename... ArgsT >
    void applyTuple( T* pObj,
                     void (T::*f)( ArgsF... ),
                     std::tr1::tuple<ArgsT...> const& t )
    {
       apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
    }
    
    //-----------------------------------------------------------------------------
    
    /**
     * Static Function Tuple Argument Unpacking
     *
     * This recursive template unpacks the tuple parameters into
     * variadic template arguments until we reach the count of 0 where the function
     * is called with the correct parameters
     *
     * @tparam N Number of tuple arguments to unroll
     *
     * @ingroup g_util_tuple
     */
    template < uint N >
    struct apply_func
    {
      template < typename... ArgsF, typename... ArgsT, typename... Args >
      static void applyTuple( void (*f)( ArgsF... ),
                              const std::tr1::tuple<ArgsT...>& t,
                              Args... args )
      {
        apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
      }
    };
    
    //-----------------------------------------------------------------------------
    
    /**
     * Static Function Tuple Argument Unpacking End Point
     *
     * This recursive template unpacks the tuple parameters into
     * variadic template arguments until we reach the count of 0 where the function
     * is called with the correct parameters
     *
     * @ingroup g_util_tuple
     */
    template <>
    struct apply_func<0>
    {
      template < typename... ArgsF, typename... ArgsT, typename... Args >
      static void applyTuple( void (*f)( ArgsF... ),
                              const std::tr1::tuple<ArgsT...>& /* t */,
                              Args... args )
      {
        f( args... );
      }
    };
    
    //-----------------------------------------------------------------------------
    
    /**
     * Static Function Call Forwarding Using Tuple Pack Parameters
     */
    // Actual apply function
    template < typename... ArgsF, typename... ArgsT >
    void applyTuple( void (*f)(ArgsF...),
                     std::tr1::tuple<ArgsT...> const& t )
    {
       apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
    }
    
    // ***************************************
    // Usage
    // ***************************************
    
    template < typename T, typename... Args >
    class Message : public IMessage
    {
    
      typedef void (T::*F)( Args... args );
    
    public:
    
      Message( const std::string& name,
               T& obj,
               F pFunc,
               Args... args );
    
    private:
    
      virtual void doDispatch( );
    
      T*  pObj_;
      F   pFunc_;
      std::tr1::tuple<Args...> args_;
    };
    
    //-----------------------------------------------------------------------------
    
    template < typename T, typename... Args >
    Message<T, Args...>::Message( const std::string& name,
                                  T& obj,
                                  F pFunc,
                                  Args... args )
    : IMessage( name ),
      pObj_( &obj ),
      pFunc_( pFunc ),
      args_( std::forward<Args>(args)... )
    {
    
    }
    
    //-----------------------------------------------------------------------------
    
    template < typename T, typename... Args >
    void Message<T, Args...>::doDispatch( )
    {
      try
      {
        applyTuple( pObj_, pFunc_, args_ );
      }
      catch ( std::exception& e )
      {
    
      }
    }
    
    0 讨论(0)
  • 2020-11-22 08:34

    How about this:

    // Warning: NOT tested!
    #include <cstddef>
    #include <tuple>
    #include <type_traits>
    #include <utility>
    
    using std::declval;
    using std::forward;
    using std::get;
    using std::integral_constant;
    using std::size_t;
    using std::tuple;
    
    namespace detail
    {
        template < typename Func, typename ...T, typename ...Args >
        auto  explode_tuple( integral_constant<size_t, 0u>, tuple<T...> const &t,
         Func &&f, Args &&...a )
         -> decltype( forward<Func>(f)(declval<T const>()...) )
        { return forward<Func>( f )( forward<Args>(a)... ); }
    
        template < size_t Index, typename Func, typename ...T, typename ...Args >
        auto  explode_tuple( integral_constant<size_t, Index>, tuple<T...> const&t,
         Func &&f, Args &&...a )
         -> decltype( forward<Func>(f)(declval<T const>()...) )
        {
            return explode_tuple( integral_constant<size_t, Index - 1u>{}, t,
             forward<Func>(f), get<Index - 1u>(t), forward<Args>(a)... );
        }
    }
    
    template < typename Func, typename ...T >
    auto  run_tuple( Func &&f, tuple<T...> const &t )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    {
        return detail::explode_tuple( integral_constant<size_t, sizeof...(T)>{}, t,
         forward<Func>(f) );
    }
    
    template < typename Tret, typename ...T >
    Tret  func_T( tuple<T...> const &t )
    { return run_tuple( &func<Tret, T...>, t ); }
    

    The run_tuple function template takes the given tuple and pass its elements individually to the given function. It carries out its work by recursively calling its helper function templates explode_tuple. It's important that run_tuple passes the tuple's size to explode_tuple; that number acts as a counter for how many elements to extract.

    If the tuple is empty, then run_tuple calls the first version of explode_tuple with the remote function as the only other argument. The remote function is called with no arguments and we're done. If the tuple is not empty, a higher number is passed to the second version of explode_tuple, along with the remote function. A recursive call to explode_tuple is made, with the same arguments, except the counter number is decreased by one and (a reference to) the last tuple element is tacked on as an argument after the remote function. In a recursive call, either the counter isn't zero, and another call is made with the counter decreased again and the next-unreferenced element is inserted in the argument list after the remote function but before the other inserted arguments, or the counter reaches zero and the remote function is called with all the arguments accumulated after it.

    I'm not sure I have the syntax of forcing a particular version of a function template right. I think you can use a pointer-to-function as a function object; the compiler will automatically fix it.

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

    Why not just wrap your variadic arguments into a tuple class and then use compile time recursion (see link) to retrieve the index you are interested in. I find that unpacking variadic templates into a container or collection may not be type safe w.r.t. heterogeneous types

    template<typename... Args>
    auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
    {
        return std::make_tuple(args);
    }
    
    0 讨论(0)
  • 2020-11-22 08:43

    I am evaluating MSVS 2013RC, and it failed to compile some of the previous solutions proposed here in some cases. For example, MSVS will fail to compile "auto" returns if there are too many function parameters, because of a namespace imbrication limit (I sent that info to Microsoft to have it corrected). In other cases, we need access to the function's return, although that can also be done with a lamda: the following two examples give the same result..

    apply_tuple([&ret1](double a){ret1 = cos(a); }, std::make_tuple<double>(.2));
    ret2 = apply_tuple((double(*)(double))cos, std::make_tuple<double>(.2));
    

    And thanks again to those who posted answers here before me, I wouldn't have gotten to this without it... so here it is:

    template<size_t N>
    struct apply_impl {
        template<typename F, typename T, typename... A>
        static inline auto apply_tuple(F&& f, T&& t, A&&... a)
        -> decltype(apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                              std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
             return apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                              std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
        }
        template<typename C, typename F, typename T, typename... A>
        static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
        -> decltype(apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                              std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
             return apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                              std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
        }
    };
    
    // This is a work-around for MSVS 2013RC that is required in some cases
    #if _MSC_VER <= 1800 /* update this when bug is corrected */
    template<>
    struct apply_impl<6> {
        template<typename F, typename T, typename... A>
        static inline auto apply_tuple(F&& f, T&& t, A&&... a)
        -> decltype(std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
               std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
             return std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
               std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
        }
        template<typename C, typename F, typename T, typename... A>
        static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
        -> decltype((o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
               std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
             return (o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
               std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
        }
    };
    #endif
    
    template<>
    struct apply_impl<0> {
        template<typename F, typename T, typename... A>
        static inline auto apply_tuple(F&& f, T&&, A&&... a)
        -> decltype(std::forward<F>(f)(std::forward<A>(a)...)) {
             return std::forward<F>(f)(std::forward<A>(a)...);
        }
        template<typename C, typename F, typename T, typename... A>
        static inline auto apply_tuple(C*const o, F&& f, T&&, A&&... a)
        -> decltype((o->*std::forward<F>(f))(std::forward<A>(a)...)) {
             return (o->*std::forward<F>(f))(std::forward<A>(a)...);
        }
    };
    
    // Apply tuple parameters on a non-member or static-member function by perfect forwarding
    template<typename F, typename T>
    inline auto apply_tuple(F&& f, T&& t)
    -> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) {
         return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
    }
    
    // Apply tuple parameters on a member function
    template<typename C, typename F, typename T>
    inline auto apply_tuple(C*const o, F&& f, T&& t)
    -> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t))) {
         return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t));
    }
    
    0 讨论(0)
提交回复
热议问题