template parameter packs access Nth type and Nth element

后端 未结 5 985
广开言路
广开言路 2020-11-28 07:59

The following paper is the first proposal I found for template parameter packs.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1603.pdf

At page 16,

相关标签:
5条回答
  • 2020-11-28 08:22

    Access N-th element?

    Using std::forward_as_tuple:

    template <int I, class... Ts>
    decltype(auto) get(Ts&&... ts) {
      return std::get<I>(std::forward_as_tuple(ts...));
    }
    

    Example usage:

    template<class...Ts>
    void foo(Ts&&...ts){
    
      auto& first = get<0>(ts...);
      auto second = get<1>(ts...);
    
      first = 'H';
      second = 'E';
    
      (std::cout << ... << ts);
    }
    
    foo('h','e','l','l','o');
    // prints "Hello"
    

    This answer is to supplement Emile Cormier's answer which gives only the n-th type.

    0 讨论(0)
  • 2020-11-28 08:35

    We can implement a simple function to get nth parameter directly without any recursive calls but many pure type operations in compile-time. Let's look at the key code firstly:

    template<class...Ts>
    struct GetImp {
      template<class T, class...Us>
      static decltype(auto) impl(Ts&&..., T&& obj, Us&&...) {
        return std::forward<T>(obj);
      }
    };
    
    template<size_t n, class...Ts>
    decltype(auto) get(Ts&&...args) {
      static_assert(n<sizeof...(args), "index over range");
      return Transform<GetImp, Before_s<n, Seq<Ts...>> >
        ::impl(std::forward<Ts>(args)...);
    }
    

    What does Transform means?

    For example, if we have a type T that is std::tuple<int,double,float>, then Transform<GetImp,T> would be GetImp<int,double,float>. note that I define another empty struct "Seq" instead of std::tuple to do the same thing with less compile time.(In fact both of them could be compiled very quickly,but I guess an empty struct would be more effectively) So Before_s<n,Seq<Ts...>> generate a Seq<?> and then we transform it into GetImp, so that we can know what type of [0]~[n-1] parameters are, and then drop them off to index the nth parameter directly. For example, Before_s<3,Seq<T0,T1,T2,T3,T4...>> is Seq<T0,T1,T2>, Before_s<2,Seq<T0,T1,T2,T3,T4...>> is Seq<T0,T1> etc. We use Before_s to deal with our Seq type to reduce compile time when we use one meta function to implement another meta function for less compile time.

    Implementation

    #define OMIT_T(...) typename __VA_ARGS__::type
    
    template<class...Args>
    struct Seq { };
    
    template< template<class...> class Dst >
    struct TransformImp{
        template< template<class...>class Src, class...Args >
        static Dst<Args...> from(Src<Args...>&&);
    };
    template< template<class...> class Dst, class T>
    using Transform = decltype(TransformImp<Dst>::from(std::declval<T>()));
    template<class T>
    using Seqfy = Transform<Seq, T>;
    
    
    template<class...>struct MergeImp;
    template<class...Ts, class...Others>
    struct MergeImp<Seq<Ts...>, Seq<Others...>>
    {
      using type = Seq<Ts..., Others...>;
    };
    template<class first, class second>
    using Merge = OMIT_T(MergeImp<Seqfy<first>, Seqfy<second> >);
    template<class T, class U>
    using Merge_s = OMIT_T(MergeImp<T, U>);
    
    template<size_t, class...>struct BeforeImp;
    
    template<size_t n, class T, class...Ts>
    struct BeforeImp<n, Seq<T, Ts...>> {
        static_assert(n <= sizeof...(Ts)+1, "index over range");
        using type = Merge_s<Seq<T>, OMIT_T(BeforeImp<n - 1, Seq<Ts...>>)>;
    };
    
    template<class T, class...Ts>
    struct BeforeImp<1, Seq<T, Ts...>> {
        using type = Seq<T>;
    };
    template<class T, class...Ts>
    struct BeforeImp<0, Seq<T, Ts...>> {
        using type = Seq<>;
    };
    template<size_t n>
    struct BeforeImp<n, Seq<>> {
        using type = Seq<>;
    };
    
    template<size_t n, class T>
    using Before = OMIT_T(BeforeImp<n, Seqfy<T>>);
    template<size_t n, class T>
    using Before_s = OMIT_T(BeforeImp<n, T>);
    

    Edited: Advanced Implementation

    We needn't use Before_s to calculate n-1 types before nth type,instead, we can ignore them:

    struct EatParam{
        constexpr EatParam(...)noexcept{}
    };
    
    template<size_t n>
    struct GenSeqImp {
      using type = Merge_s<OMIT_T(GenSeqImp<n / 2>), OMIT_T(GenSeqImp<n - n / 2>)>;
    };
    template<>
    struct GenSeqImp<0> {
      using type = Seq<>;
    };
    template<>
    struct GenSeqImp<1> {
      using type = Seq<EatParam>;
    };
    
    template<size_t n>
    using GenSeq = OMIT_T(GenSeqImp<n>);
    
    
    template<class...Ts>
    struct GetImp {
      template<class T>
      static constexpr decltype(auto) impl(Ts&&..., T&& obj, ...)noexcept {
        return std::forward<T>(obj);
      }
    };
    
    
    template<size_t n, class...Ts>
    constexpr decltype(auto) get(Ts&&...args)noexcept {
      static_assert(n<sizeof...(args), "index over range.");
      //return Transform<GetImp, Before_s<n, Seq<Ts...>> >
      return Transform<GetImp, GenSeq<n>>
        ::impl(std::forward<Ts>(args)...);
    }
    

    In addition , there is a very interesting article about implementation of getting nth type:

    Thanks for their work, I didn't know we could use (...) to do the hack before.

    0 讨论(0)
  • 2020-11-28 08:46

    C++11 doesn't have corresponding operators which is the reason they are proposed. With C++11 you'll need to either extract the corresponding information yourself or use a class which already does the necessary operation. The easiest approach is probably to just use std::tuple<T...> which already implements the corresponding logic.

    If you wonder how std::tuple<T...> currently implements these operations: it is basically an exercise in functional programming using a fairly bad functional programming notation. Once you know how to get the n-th type of the sequence, getting the n-th element using inheritance from base classes parameterized on index and type is fairly trivial. Implementing something like tuple_element<N, T...> could look something like this:

    template <int N, typename... T>
    struct tuple_element;
    
    template <typename T0, typename... T>
    struct tuple_element<0, T0, T...> {
        typedef T0 type;
    };
    template <int N, typename T0, typename... T>
    struct tuple_element<N, T0, T...> {
        typedef typename tuple_element<N-1, T...>::type type;
    };
    

    The actual more challenging bit in implementing something like std::tuple<T...> is conjuring up a list of indices so you got a parallel list of type and integers which can then be expanded, e.g., for a list of base classes using something like (how the internal details look exactly will differ but the basic idea of having a parallel parameters packs for the types and their indices will be somehow there):

    template <typename... T, int... I>
    class tuple_base<tuple_types<T...>, tuple_indices<I...>>:
         public tuple_field<T, I>... {
    };
    
    0 讨论(0)
  • 2020-11-28 08:49

    Others have already answered that it can be done via std::tuple. If you want to access the Nth type of a parameter pack, you may find the following metafunction handy:

    template<int N, typename... Ts> using NthTypeOf =
            typename std::tuple_element<N, std::tuple<Ts...>>::type;
    

    Usage:

    using ThirdType = NthTypeOf<2, Ts...>;
    
    0 讨论(0)
  • 2020-11-28 08:49

    To get the Nth element from a pack you can write:

    Option 1

    Using tuple_element for getting the return type for the Nth element:

    template<size_t index, typename T, typename... Ts>
    inline constexpr typename enable_if<index==0, T>::type
    get(T&& t, Ts&&... ts) {
        return t;
    }
    
    template<size_t index, typename T, typename... Ts>
    inline constexpr typename enable_if<(index > 0) && index <= sizeof...(Ts),
              typename tuple_element<index, tuple<T, Ts...>>::type>::type
    get(T&& t, Ts&&... ts) {
        return get<index-1>(std::forward<Ts>(ts)...);
    }
    
    // below is optional - just for getting a more readable compilation error
    // in case calling get with a bad index
    
    inline template<long long index, typename... Ts>
    constexpr bool index_ok() {
        return index >= 0 && index < sizeof...(Ts);
    }
    
    template<long long index, typename T, typename... Ts>
    inline constexpr
    typename enable_if<!index_ok<index, T, Ts...>(), T>::type
    get(T&& t, Ts&&... ts) {
        static_assert(index_ok<index, T, Ts...>(),
            "bad index in call to get, smaller than zero or above pack size");
        return t;
    }
    

    Option 2

    Without using tuple, relying on auto return type and specifically on C++14 decltype(auto) and on using enable_if as a template parameter and not as a return type:

    template<size_t index, typename T, typename... Ts,
        typename enable_if<index==0>::type* = nullptr>
    inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
        return std::forward<T>(t); 
    }
    
    template<size_t index, typename T, typename... Ts,
        typename enable_if<(index > 0 && index <= sizeof...(Ts))>::type* = nullptr>
    inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
        return get<index-1>(std::forward<Ts>(ts)...);
    }
    
    template<long long index, typename... Ts>
    inline constexpr bool index_ok() {
        return index >= 0 && index < (long long)sizeof...(Ts);
    }
    
    // block (compilation error) the call to get with bad index,
    // providing a readable compilation error
    template<long long index, typename T, typename... Ts,
        typename enable_if<(!index_ok<index, T, Ts...>())>::type* = nullptr>
    inline constexpr decltype(auto) get(T&& t, Ts&&... ts) {
        static_assert(index_ok<index, T, Ts...>(),
            "bad index in call to get, smaller than zero or above pack size");
        return std::forward<T>(t); // need to return something...
                                   // we hope to fail on the static_assert above
    }
    

    Usage example:

    template<size_t index, typename... Ts>
    void resetElementN(Ts&&... ts) {
        get<index>(std::forward<Ts>(ts)...) = {}; // assuming element N has an empty ctor
    }
    
    int main() {
        int i = 0;
        string s = "hello";
        get<0>(i,2,"hello","hello"s, 'a') += get<0>(2);
        get<1>(1,i,"hello",4) += get<1>(1, 2);
        get<3>(1,2,"hello",i) += get<2>(0, 1, 2);    
        get<2>(1,2,s,4) = get<2>(0, 1, "hi");
        cout << i << ' ' << s << endl;    
        resetElementN<1>(0, i, 2);
        resetElementN<0>(s, 1, 2);
        cout << i << ' ' << s << endl;    
    
        // not ok - and do not compile
        // get<0>(1,i,"hello","hello"s) = 5;
        // get<1>(1,i*2,"hello") = 5;
        // get<2>(1,i*2,"hello")[4] = '!';
        // resetElementN<1>(s, 1, 2);
    
        // ok
        const int j = 2;
        cout << get<0>(j,i,3,4) << endl;
    
        // not ok - and do not compile
        // get<0>(j,i,3,4) = 5;    
    
        // not ok - and do not compile
        // with a readable compilation error
        // cout << get<-1>("one", 2, '3') << endl;
        // cout << get<3>("one", 2, '3') << endl;
    }
    

    Code
    Option 1: http://coliru.stacked-crooked.com/a/60ad3d860aa94453
    Option 2: http://coliru.stacked-crooked.com/a/09f6e8e155612f8b

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