effective way to select last parameter of variadic template
I know how to select first parameter of variadic template:
template< class...Args> struct select_first;
template< class A, class ...Args> struct select_first<A,Args...>{ using type = A;};
It's a very simple. However, select_last is not similar:
template< class ...Args> struct select_last;
template< class A> struct select_last<A> { using type = A; };
template< class A, class Args...> struct select_last<A,Args...>{
using type = typename select_last<Args...>::type;
};
This solution needed deep recursive template instantinations. I try to solve this with as:
template< class A, class Args...>
struct select_last< Args ... , A>{ using type = A; }; // but it's not compiled.
Q: exist more effective way to selecting last parameter of variadic templates?
With C++17, the cleanest way is
template<typename T>
struct tag
{
using type = T;
};
template<typename... Ts>
struct select_last
{
// Use a fold-expression to fold the comma operator over the parameter pack.
using type = typename decltype((tag<Ts>{}, ...))::type;
};
with O(1) instantiation depth.
Same approach as last time, O(logN) instantiation depth. Using only one overload, so it should consume less resources.
Warning: it currently removes references from the tuple types.
Note: Removed the reference from pack::declval
. I think it still works in every case.
indices trick in O(log(N)) instantiations, by Xeo; modified to use std::size_t
instead of unsigned
#include <cstddef>
// using aliases for cleaner syntax
template<class T> using Invoke = typename T::type;
template<std::size_t...> struct seq{ using type = seq; };
template<class S1, class S2> struct concat;
template<std::size_t... I1, std::size_t... I2>
struct concat<seq<I1...>, seq<I2...>>
: seq<I1..., (sizeof...(I1)+I2)...>{};
template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;
template<std::size_t N> struct gen_seq;
template<std::size_t N> using GenSeq = Invoke<gen_seq<N>>;
template<std::size_t N>
struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};
template<> struct gen_seq<0> : seq<>{};
template<> struct gen_seq<1> : seq<0>{};
Today, I realized there's a different, simpler and probably faster (compilation time) solution to get the nth type of a tuple (basically an implementation of std::tuple_element
). Even though it's a direct solution of another question, I'll also post it here for completeness.
namespace detail
{
template<std::size_t>
struct Any
{
template<class T> Any(T&&) {}
};
template<typename T>
struct wrapper {};
template<std::size_t... Is>
struct get_nth_helper
{
template<typename T>
static T deduce(Any<Is>..., wrapper<T>, ...);
};
template<std::size_t... Is, typename... Ts>
auto deduce_seq(seq<Is...>, wrapper<Ts>... pp)
-> decltype( get_nth_helper<Is...>::deduce(pp...) );
}
#include <tuple>
template<std::size_t n, class Tuple>
struct tuple_element;
template<std::size_t n, class... Ts>
struct tuple_element<n, std::tuple<Ts...>>
{
using type = decltype( detail::deduce_seq(gen_seq<n>{},
detail::wrapper<Ts>()...) );
};
Helper for last element:
template<typename Tuple>
struct tuple_last_element;
template<typename... Ts>
struct tuple_last_element<std::tuple<Ts...>>
{
using type = typename tuple_element<sizeof...(Ts)-1,
std::tuple<Ts...>> :: type;
};
Usage example:
#include <iostream>
#include <type_traits>
int main()
{
std::tuple<int, bool, char const&> t{42, true, 'c'};
tuple_last_element<decltype(t)>::type x = 'c'; // it's a reference
static_assert(std::is_same<decltype(x), char const&>{}, "!");
}
Original version:
#include <tuple>
#include <type_traits>
namespace detail
{
template<typename Seq, typename... TT>
struct get_last_helper;
template<std::size_t... II, typename... TT>
struct get_last_helper< seq<II...>, TT... >
{
template<std::size_t I, std::size_t L, typename T>
struct pack {};
template<typename T, std::size_t L>
struct pack<L, L, T>
{
T declval();
};
// this needs simplification..
template<typename... TTpacked>
struct exp : TTpacked...
{
static auto declval_helper()
-> decltype(std::declval<exp>().declval());
using type = decltype(declval_helper());
};
using type = typename exp<pack<II, sizeof...(TT)-1, TT>...>::type;
};
}
template< typename Tuple >
struct get_last;
template< typename... TT >
struct get_last<std::tuple<TT...>>
{
template<std::size_t... II>
static seq<II...> helper(seq<II...>);
using seq_t = decltype(helper(gen_seq<sizeof...(TT)>()));
using type = typename detail::get_last_helper<seq_t, TT...>::type;
};
int main()
{
using test_type = std::tuple<int, double, bool, char>;
static_assert(std::is_same<char, get_last<test_type>::type>::value, "!");
// fails:
static_assert(std::is_same<int, get_last<test_type>::type>::value, "!");
}
If you are willing to strip references blindly from your type list (which is quite often the case: either you know they are references, or you don't care), you can do this with little machinery outside of std
. Basically stuff the data into a tuple
or tie
, then use std::get<sizeof...(X)-1>( tuple or tie )
to extract the last element.
You can do this in a pure-type context using std::declval< std::tuple<Args...> >()
and decltype
, and possibly std::remove_reference
.
As an example, suppose you have a variardic set of arguments, and you want to return the last argument ignoring the rest:
#define RETURNS(x) ->decltype(x) { return (x); }
template<typename ...Args>
auto get_last( Args&&... args )
RETURNS( std::get< sizeof...(Args)-1 >( std::tie(std::forward<Args>(args)...) ) )
we can then use this in another function:
template<typename ...Args>
void foo( Args&&... args ) {
auto&& last = get_last(std::forward<Args>(args)...);
}
This other solution is brilliant if C++17 is available and if one is interested in the last type only.
If C++14 support is required (C++11 plus index_sequence
) or if one is interested in the nth type then a good solution is
#include <utility>
////////////////////////////////////////////////////////////////////////////////
template<std::size_t n, std::size_t i, class>
struct type_if_equal {
static_assert(n != i, "see specialization");
// missing `type` typedef by purpose
};
template<std::size_t n, class T>
struct type_if_equal<n, n, T> {
using type = T;
};
////////////////////////////////////////////////////////////////////////////////
template<std::size_t n, class Is, class... Ts>
struct select_nth;
template<std::size_t n, std::size_t... is, class... Ts>
struct select_nth<n, std::index_sequence<is...>, Ts...>
: type_if_equal<n, is, Ts>...
{};
template<std::size_t n, class... Ts>
using select_nth_t = typename select_nth<
n, std::index_sequence_for<Ts...>, Ts...
>::type;
////////////////////////////////////////////////////////////////////////////////
template<class T0, class... Ts>
using select_last_t = select_nth_t<sizeof...(Ts), T0, Ts...>;
////////////////////////////////////////////////////////////////////////////////
int main() {
using T = select_last_t<int, double, double, long, long, long, int, char>;
static_assert(std::is_same<T, char>{}, "");
}
Warning: Do not use a naive self-made solution like select_nth_t
if you need fast compilation times for huge variadic lists. There are highly optimized template-metaprogramming libraries for this purpose. Have a look at metaben.ch for a comparison of compile-time performance of several algorithms. This algorithm is called at
, and here is my measurement result of select_nth_t
based on this code using GCC 10:
See blog posts by Louis Dionne and Odin Holmes for excellent background information regarding compile-time reduction of the at
algorithm (a.k.a. std::tuple_element_t
).