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.
std::tuple
and usestd::get
andstd::tuple_element
, which are implemented recursively. Also see this answer – dypstd::get
andstd::tuple_element
don't inherently need to be recursive, they can be implemented without recursion through indices and overload resolution / derived-to-base conversions. – Xeostd::tuple_element<...>
which would be the recursive function. – Dietmar Kühlstd::tuple_element
with type deduction and indices -- using your O(logN) indices generator ;) But then the indices need to be created recursively (or manually). – dyp