Reputation: 11736
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?
Upvotes: 61
Views: 43695
Reputation: 2065
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-expression
s rather than arbitrarily complex expressions.
If you want more info, read it on P2662R3.
As of now (updated December 2024), this feature has been implemented by Clang 19 and GCC 15.
Upvotes: 4
Reputation: 1590
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.
Upvotes: 27
Reputation: 66
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.
Upvotes: 3
Reputation: 13752
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;
}
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
}
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
Upvotes: 7
Reputation: 29209
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...>;
Upvotes: 62
Reputation: 153792
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>... {
};
Upvotes: 39