How to reverse the order of arguments of a variadic template function?

前端 未结 5 1155
猫巷女王i
猫巷女王i 2020-11-27 16:42

I have a template function with varargs template arguments, like this

template
void ascendingPrint(         


        
5条回答
  •  再見小時候
    2020-11-27 17:42

    Overall approach and usage


    The overal approach consists in packing the arguments into an std::tuple of references, exploiting the perfect forwarding machinery of std::forward_as_tuple().

    This means that, at run-time, you should incur in very small overhead and no unnecessary copy/move operations. Also, the framework does not use recursion (apart from compile-time recursion, which is unavoidable for generating indices), so no risk of run-time overhead even in case the compiler would not manage to inline the recursive function calls (which is unlikely anyway, so this is more of an academic argument).

    Moreover, this solution is general, in that you can use it as a header-only library to invoke your functions with reversed arguments and with minimum effort: descending_print() should be just a minimal thin wrapper around ascending_print().

    Here is how it should look like:

    MAKE_REVERT_CALLABLE(ascending_print)
    
    template
    void descending_print(Args&&... args)
    {
        revert_call(REVERT_ADAPTER(ascending_print), std::forward(args)...);
    } 
    

    What follows is a presentation of the implementation.


    First step: reverting a type sequence


    Here is a simple way to revert a type sequence:

    #include 
    #include 
    
    template
    struct append_to_type_seq { };
    
    template
    struct append_to_type_seq>
    {
        using type = std::tuple;
    };
    
    template
    struct revert_type_seq
    {
        using type = std::tuple<>;
    };
    
    template
    struct revert_type_seq
    {
        using type = typename append_to_type_seq<
            T,
            typename revert_type_seq::type
            >::type;
    };
    

    A small test program:

    int main()
    {
        static_assert(
            std::is_same<
                revert_type_seq::type,
                std::tuple
                >::value,
            "Error"
            );
    }
    

    And a live example.


    Second step: reverting a tuple


    The next step consists in reverting a tuple. Given the usual indices trick machinery:

    template 
    struct index_list { };
    
    namespace detail
    {
        template 
        struct range_builder;
    
        template 
        struct range_builder
        {
            typedef index_list type;
        };
    
        template 
        struct range_builder : public range_builder
        { };
    }
    
    template
    using index_range = typename detail::range_builder::type;
    

    Together with the functions defined above, a tuple can easily be reverted this way:

    template
    typename revert_type_seq::type
    revert_tuple(std::tuple t, index_list)
    {
        using reverted_tuple = typename revert_type_seq::type;
    
        // Forwarding machinery that handles both lvalues and rvalues...
        auto rt = std::forward_as_tuple(
                std::forward<
                    typename std::conditional<
                        std::is_lvalue_reference<
                            typename std::tuple_element::type
                            >::value,
                        typename std::tuple_element::type,
                        typename std::remove_reference<
                            typename std::tuple_element::type
                            >::type
                        >::type
                    >(std::get(t))...
            );
    
        return rt;
    }
    
    template
    typename revert_type_seq::type
    revert_tuple(std::tuple t)
    {
        return revert_tuple(t, index_range<0, sizeof...(Args)>());
    }
    

    Here is a simple test program:

    #include 
    
    int main()
    {
        std::tuple t(42, 1729, 'c');
        auto rt = revert_tuple(t);
    
        std::cout << std::get<0>(rt) << " "; // Prints c
        std::cout << std::get<1>(rt) << " "; // Prints 1729
        std::cout << std::get<2>(rt) << " "; // Prints 42
    }
    

    Here is a live example.


    Third step: reverting a function's arguments


    The final step consists in unpacking the tuple when calling our target function. Here is another generic utility to save us a couple of lines:

    template
    typename revert_type_seq::type
    make_revert(Args&&... args)
    {
        auto t = std::forward_as_tuple(std::forward(args)...);
        return revert_tuple(t);
    }
    

    The above function creates a tuple whose elements are the arguments provided, but in reverse order. We are not ready to define our target:

    template
    void ascending_print(T&& t)
    {
        std::cout << std::forward(t) << " ";
    }
    
    template
    void ascending_print(T&& t, Args&&... args)
    {
        ascending_print(std::forward(t));
        ascending_print(std::forward(args)...);
    }
    

    The above function(s) prints all the arguments provided. And here is how we could write descending_print():

    template
    void call_ascending_print(T&& t, index_list)
    {
        ascending_print(std::get(std::forward(t))...);
    }
    
    template
    void descending_print(Args&&... args) {
        call_ascending_print(make_revert(std::forward(args)...),
             index_range<0, sizeof...(Args)>());
    }
    

    A simple test case again:

    int main()
    {
        ascending_print(42, 3.14, "Hello, World!");
        std::cout << std::endl;
        descending_print(42, 3.14, "Hello, World!");
    }
    

    And of course a live example.


    Final step: simplification


    The above solution may be non-trivial to understand, but it can be made trivial to use, and quite flexible. Given a couple of generic functions:

    template
    void revert_call(F&& f, index_list, Args&&... args)
    {
        auto rt = make_revert(std::forward(args)...);
        f(std::get(rt)...);
    }
    
    template
    void revert_call(F&& f, Args&&... args)
    {
        revert_call(f, index_range<0, sizeof...(Args)>(), 
                    std::forward(args)...);
    }
    

    And a couple of macro definitions (I couldn't find a way to create an overload set for a function template, sorry):

    #define MAKE_REVERT_CALLABLE(func) \
        struct revert_caller_ ## func \
        { \
            template void operator () (Args&&... args) \
            { func(std::forward(args)...); } \
        };
    
    #define REVERT_ADAPTER(func) \
        revert_caller_ ## func()
    

    It becomes really easy to adapt any function for being called with arguments in reverse order:

    MAKE_REVERT_CALLABLE(ascending_print)
    
    template
    void descending_print(Args&&... args)
    {
        revert_call(REVERT_ADAPTER(ascending_print), std::forward(args)...);
    }
    
    int main()
    {
        ascending_print(42, 3.14, "Hello, World!");
        std::cout << std::endl;
        descending_print(42, 3.14, "Hello, World!");
    }
    

    To conclude, as usual, a live example.

提交回复
热议问题