effective way to select last parameter of variadic template

后端 未结 8 882
温柔的废话
温柔的废话 2020-12-01 12:37

I know how to select first parameter of variadic template:

template< class...Args> struct select_first;
template< class A, class ...Args> struct          


        
相关标签:
8条回答
  • 2020-12-01 13:10

    Sorry for being a bit late to the party, but I just ran across the same problem , looked up for an answer, didn't like what I see here and realised it can be done using a tuple. Please see C++11 implementation below. Note: one can also get access to an Nth type of a variadic template this way. (The example doesn't check that N exceeds the number of variadic arguments , however the check can be done with SFINAE technique (enable_if) for instance) Is that an acceptable answer or I'm missing anything in the question?

    #include <tuple>
    #include <iostream>
    
    struct A
    {
        char ch = 'a';
    };
    struct B
    {
        char ch = 'b';
    };
    struct C
    {
        char ch = 'c';
    };
    
    
    template <typename... Types>
    struct SomeVariadic {
    
        using TypesTuple = std::tuple<Types...>;
    
        using LastType = typename std::tuple_element<sizeof...(Types)-1, TypesTuple>::type;
    
        template <int N>
        using NthType = typename std::tuple_element<N, TypesTuple>::type;
    };
    
    
    
    int main(int argc, char* argv[]) {
    
        SomeVariadic<A,B,C>::LastType l;
    
        std::cout << SomeVariadic<A,B,C>::LastType().ch << " "
                << SomeVariadic<A,B,C>::NthType<1>().ch<< std::endl;
    }
    
    0 讨论(0)
  • 2020-12-01 13:11

    Same approach as last time, O(logN) instantiation depth. Using only one overload, so it should consume less resources.

    Warning: it currently removes references from the tuple types. Note: Removed the reference from pack::declval. I think it still works in every case.

    indices trick in O(log(N)) instantiations, by Xeo; modified to use std::size_t instead of unsigned

        #include <cstddef>
    
        // using aliases for cleaner syntax
        template<class T> using Invoke = typename T::type;
    
        template<std::size_t...> struct seq{ using type = seq; };
    
        template<class S1, class S2> struct concat;
    
        template<std::size_t... I1, std::size_t... I2>
        struct concat<seq<I1...>, seq<I2...>>
          : seq<I1..., (sizeof...(I1)+I2)...>{};
    
        template<class S1, class S2>
        using Concat = Invoke<concat<S1, S2>>;
    
        template<std::size_t N> struct gen_seq;
        template<std::size_t N> using GenSeq = Invoke<gen_seq<N>>;
    
        template<std::size_t N>
        struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};
    
        template<> struct gen_seq<0> : seq<>{};
        template<> struct gen_seq<1> : seq<0>{};
    

    Today, I realized there's a different, simpler and probably faster (compilation time) solution to get the nth type of a tuple (basically an implementation of std::tuple_element). Even though it's a direct solution of another question, I'll also post it here for completeness.

    namespace detail
    {
        template<std::size_t>
        struct Any
        {
            template<class T> Any(T&&) {}
        };
    
        template<typename T>
        struct wrapper {};
    
        template<std::size_t... Is>
        struct get_nth_helper
        {
            template<typename T>
            static T deduce(Any<Is>..., wrapper<T>, ...);
        };
    
        template<std::size_t... Is, typename... Ts>
        auto deduce_seq(seq<Is...>, wrapper<Ts>... pp)
        -> decltype( get_nth_helper<Is...>::deduce(pp...) );
    }
    
    #include <tuple>
    
    template<std::size_t n, class Tuple>
    struct tuple_element;
    
    template<std::size_t n, class... Ts>
    struct tuple_element<n, std::tuple<Ts...>>
    {
        using type = decltype( detail::deduce_seq(gen_seq<n>{},
                                                  detail::wrapper<Ts>()...) );
    };
    

    Helper for last element:

    template<typename Tuple>
    struct tuple_last_element;
    
    template<typename... Ts>
    struct tuple_last_element<std::tuple<Ts...>>
    {
        using type = typename tuple_element<sizeof...(Ts)-1,
                                            std::tuple<Ts...>> :: type;
    };
    

    Usage example:

    #include <iostream>
    #include <type_traits>
    int main()
    {
        std::tuple<int, bool, char const&> t{42, true, 'c'};
    
        tuple_last_element<decltype(t)>::type x = 'c'; // it's a reference
    
        static_assert(std::is_same<decltype(x), char const&>{}, "!");
    }
    

    Original version:

    #include <tuple>
    #include <type_traits>
    
    namespace detail
    {
        template<typename Seq, typename... TT>
        struct get_last_helper;
    
        template<std::size_t... II, typename... TT>
        struct get_last_helper< seq<II...>, TT... >
        {
            template<std::size_t I, std::size_t L, typename T>
            struct pack {};
            template<typename T, std::size_t L>
            struct pack<L, L, T>
            {
                T declval();
            };
    
            // this needs simplification..
            template<typename... TTpacked>
            struct exp : TTpacked...
            {
                static auto declval_helper()
                    -> decltype(std::declval<exp>().declval());
                using type = decltype(declval_helper());
            };
    
            using type = typename exp<pack<II, sizeof...(TT)-1, TT>...>::type;
        };
    }
    
    template< typename Tuple >
    struct get_last;
    
    template< typename... TT >
    struct get_last<std::tuple<TT...>>
    {
        template<std::size_t... II>
        static seq<II...> helper(seq<II...>);
        using seq_t = decltype(helper(gen_seq<sizeof...(TT)>()));
    
        using type = typename detail::get_last_helper<seq_t, TT...>::type;
    };
    
    
    int main()
    {
        using test_type = std::tuple<int, double, bool, char>;
    
        static_assert(std::is_same<char, get_last<test_type>::type>::value, "!");
        // fails:
        static_assert(std::is_same<int, get_last<test_type>::type>::value, "!");
    }
    
    0 讨论(0)
  • 2020-12-01 13:16

    With C++17, the cleanest way is

    template<typename T>
    struct tag
    {
        using type = T;
    };
    
    template<typename... Ts>
    struct select_last
    {
        // Use a fold-expression to fold the comma operator over the parameter pack.
        using type = typename decltype((tag<Ts>{}, ...))::type;
    };
    

    with O(1) instantiation depth.

    0 讨论(0)
  • 2020-12-01 13:29
    template <class... Args>
    struct select_last;
    
    template <typename T>
    struct select_last<T>
    {
         using type = T;
    };
    
    template <class T, class... Args>
    struct select_last<T, Args...>
    {
        using type = typename select_last<Args...>::type;
    };
    
    0 讨论(0)
  • 2020-12-01 13:29

    The following is another lean C++17 approach which also uses a fold-expression; but avoids an ad-hoc class proxy, by using std::enable_if:

    template <typename ...Ts>
    struct select_last
    {
      using type = typename decltype((std::enable_if<true,Ts>{}, ...))::type;
    };
    
    template <typename ...Ts>
    using select_last_t = typename select_last<Ts...>::type;
    
    static_assert(std::is_same_v<char, select_last_t<int,double,char>>);
    

    In C++20 std::type_identity offers a more readable approach:

    // C++20
    template <typename ...Ts>
    struct select_last
    {
      using type = typename decltype((std::type_identity<Ts>{}, ...))::type;
    };
    
    0 讨论(0)
  • 2020-12-01 13:33

    If you are willing to strip references blindly from your type list (which is quite often the case: either you know they are references, or you don't care), you can do this with little machinery outside of std. Basically stuff the data into a tuple or tie, then use std::get<sizeof...(X)-1>( tuple or tie ) to extract the last element.

    You can do this in a pure-type context using std::declval< std::tuple<Args...> >() and decltype, and possibly std::remove_reference.

    As an example, suppose you have a variardic set of arguments, and you want to return the last argument ignoring the rest:

    #define RETURNS(x) ->decltype(x) { return (x); }
    
    template<typename ...Args>
    auto get_last( Args&&... args )
      RETURNS( std::get< sizeof...(Args)-1 >( std::tie(std::forward<Args>(args)...) ) )
    

    we can then use this in another function:

    template<typename ...Args>
    void foo( Args&&... args ) {
      auto&& last = get_last(std::forward<Args>(args)...);
    }
    
    0 讨论(0)
提交回复
热议问题