template parameter packs access Nth type and Nth element

C++C++11Variadic Templates

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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...>;

Solution 2 - C++

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>... {
};

Solution 3 - C++

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.

Solution 4 - C++

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<br/> Option 2: http://coliru.stacked-crooked.com/a/09f6e8e155612f8b<br/>

Solution 5 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnurettinView Question on Stackoverflow
Solution 1 - C++Emile CormierView Answer on Stackoverflow
Solution 2 - C++Dietmar KühlView Answer on Stackoverflow
Solution 3 - C++tomView Answer on Stackoverflow
Solution 4 - C++Amir KirshView Answer on Stackoverflow
Solution 5 - C++LandersingView Answer on Stackoverflow