template parameter packs access Nth type and Nth element
Asked Answered
S

6

60

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, it talks about introducing two new operators [] and <> for accessing parameter pack elements and parameter pack types.

The suggested syntax for such an operator involves two new operators: .[] to access values and .<> to access types. For instance:

template<int N, typename Tuple> struct tuple_element;
template<int N, ... Elements>
struct tuple_element<tuple<Elements...> >
{
    typedef Elements.<N> type;
};

template<int N, ... Elements>
Elements.<N>& get(tuple<Elements...>& t)
{ return t.[N]; }

template<int N, ... Elements>
const Elements.<N>& get(const tuple<Elements...>& t)
{ return t.[N]; }

So where are these operators? If there is none, what is their replacement?

Sentinel answered 23/11, 2013 at 13:29 Comment(4)
Their replacement is probably to put the parameter pack into a std::tuple and use std::get and std::tuple_element, which are implemented recursively. Also see this answerPulvinus
@DyP: std::get and std::tuple_element don't inherently need to be recursive, they can be implemented without recursion through indices and overload resolution / derived-to-base conversions.Purchasable
@Xeo: Since variadic argument lists [currently] don't support access by index, I'm not aware of any implementation which isn't either recursive or types out all types (which will then fallback to a recursive approach to cover the theoretically unlimited amount of arguments). Although I'd implement the member access through base classes, determining the correct type to access would use std::tuple_element<...> which would be the recursive function.Nava
@Purchasable As you can see from the answer below the linked answer, I did implement an equivalent to std::tuple_element with type deduction and indices -- using your O(logN) indices generator ;) But then the indices need to be created recursively (or manually).Pulvinus
T
39

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>... {
};
Tertial answered 23/11, 2013 at 14:24 Comment(0)
T
61

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...>;
Trample answered 20/4, 2015 at 16:30 Comment(3)
Is there a way to make it work in runtime? i.e. it accepts non-constexpr as the first argument?Biochemistry
@Biochemistry I'd need to know more about what you're trying to do. It would be best if you asked a separate question with those details (if it has not been already asked).Trample
posted: #44552408Biochemistry
T
39

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>... {
};
Tertial answered 23/11, 2013 at 14:24 Comment(0)
M
27

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.

Mainspring answered 27/12, 2017 at 12:57 Comment(5)
ts should always be passed by forward: forward<Ts>(ts)...Madwort
Would you explain why? I believe that In this case it does not matter, elements of ts... are char so extra copy instead of move does not matter. Infact It can be harmful If you use forward<Ts>(ts)... then the content of the second element is moved to second. Thus if elements of ts... were more complicated objects then you would access invalid memory in the command (std::cout << ... << ts);Mainspring
Sorry, it's my fault. String literal is always lvalue.Madwort
This is a good solution. Although when I tried it with 0 variadic arguments, std::get gave a static assert: static_assert failed due to requirement '_Always_false<std::integral_constant<unsigned long long, 0>>' "tuple index out of bounds" I created another one to handle 0 arg: template <int n> decltype(auto) get() { return 0; }Impersonalize
You should std::forward the ts ... into forward_as_tuple. Otherwise they're always passed as reference.Ewart
O
7

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

Ommatidium answered 15/6, 2016 at 13:2 Comment(0)
R
3

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.

Refresh answered 13/8, 2016 at 14:8 Comment(0)
A
2

Since C++26, the language support of pack indexing has been integrated in the current draft:

template <std::size_t I, typename... Args>
constexpr decltype(auto) pack_get(Args&&... args) noexcept {
  return std::forward<Args...[I]>(args...[I]);
}

You might wonder why you can't just code with std::forward<Args>(args)...[I] instead of std::forward<Args...[I]>(args...[I]). The former is invalid because the feature only focuses on indexing pack of id-expressions rather than arbitrarily complex expressions.

If you want more info, read it on P2662R3.

As of now (updated July 12, 2024), this feature hasn't been implemented by major compilers except Clang 19.

Assentation answered 19/12, 2023 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.