Effective Way to Select Last Parameter of Variadic Template

How to change the last argument in the parameter pack?

You might forward your arguments as tuple and then unpack all except the last one using std::integer_sequence. This code looks much simpler than your approach:

template<typename... Args>
void f1(Args... args)
{
boost::fusion::vector<Args...> v(args...);
std::cout << boost::fusion::accumulate(v, 0, [](auto i1, auto i2) { return i1 + i2; }) << std::endl;
}

template<typename Tuple, size_t... idx>
void callImpl(Tuple&& tuple, std::index_sequence<idx...>)
{
f1(std::get<idx>(std::forward<Tuple>(tuple))..., 10);
}

template<typename... Ts>
void callWithLast10(Ts&&... ts)
{
callImpl(std::forward_as_tuple(ts...), std::make_index_sequence<sizeof...(Ts) - 1>());
}

Usage:

f1(1, 2, 3, 4); // Prints 10
callWithLast10(1, 2, 3, 4); // Prints 16

Does this method of getting last element in template parameter pack have hidden overhead?

It seems to be simple and short, but does this method have any hidden overhead versus classic solutions with template specialization and helper functions/classes?

It has a drawback that could be annoying in some cases. If your type has reference qualifiers on member methods, you can encounter problems by getting an lvalue reference out of it.

Let's consider the following example:

#include<utility>
#include<tuple>

struct S {
void foo() && {}
};

template<typename T>
void f(T &&t) {
std::forward<T>(t).foo();
}

int main() {
f(S{});
}

Everything works fine for we have originally an rvalue reference and by forwarding the forwarding reference we can safely call the foo member method.

Let's consider now your snippet (note, the following code doesn't compile - continue reading):

#include<utility>
#include<tuple>

struct S {
void foo() && {}
};

template<typename... T>
void g(T&&... t) {
auto &last = std::get<sizeof...(T) - 1 >(std::tie(t...));
last.foo();
}

int main() {
g(S{});
}

This won't compile for foo cannot be invoked anymore on a variable having type S & because of the reference qualifier.

On the other side, by forwarding and extracting the last parameter somehow you can keep intact its type and you don't have such a problem.

As an example:

template<typename... T>
void g(T&&... t) {
std::get<sizeof...(T) - 1>(std::forward_as_tuple(std::forward<T>(t)...)).foo();
}

Get Last element of parameter pack in C++17 / C++20

Wrapping args in any function call would remove the warning.

You could make an identity function specifically for this purpose, but you might as well use std::forward and get proper forwarding as a bonus.

template<typename... Args>
decltype(auto) last(Args&&... args){
return (std::forward<Args>(args), ...);
}

Is there a way to remove the last template parameter of variadic template methods?

Here's a C++11 implementation depending only on std::string and std::unordered_map. Some mandatory remarks:

  • As mentioned, this is extremely brittle due to inferring the function type by the provided arguments. This is UB waiting to happen.
  • method really shouldn't be a pointer.
  • If your return type is not assignable, this will break spectacularly.
  • The class pointer really should be a reference instead.
  • If you think the implementation is insane, then yes, it is indeed, and you should give up on being fully generic.

A C++11 implementation of std::index_sequence and friends can be found here.

See it in action.

template<typename...>
struct typelist {};

template<size_t, typename, typename, typename, typename>
struct call;

template<size_t N, typename R, typename C, typename... Accum, typename Head, typename... Tail>
struct call<N, R, C, typelist<Accum...>, typelist<Head, Tail...>>
: call<N, R, C, typelist<Accum..., Head>, typelist<Tail...>>
{
};

template<typename R, typename C, typename... Accum, typename Head, typename... Tail>
struct call<sizeof...(Accum), R, C, typelist<Accum...>, typelist<Head, Tail...>>
{
template<typename... Ts>
int operator()(Ts&&...)
{
return 0;
}

template<typename... Ts>
int operator()(R& ret, void (EmptyClass::* g)(), C& obj, Accum&... args, Ts&&...)
{
auto f = (R (C::*)(Accum...))g;
ret = (obj.*f)(std::move(args)...);
return 0;
}
};

template<typename R, typename C, typename... Args, size_t... Is>
R switcher(int i, index_sequence<Is...>, void (EmptyClass::* g)(), C& obj, Args&... args)
{
R ret{};
int unused[] = {(i == Is ?
call<Is, R, C, typelist<>, typelist<Args..., void>>{}(ret, g, obj, args...)
: 0)...};

(void)unused;
return ret;
}

template<typename C, typename R, typename... Args>
void reg(std::string name, R (C::* func)(Args... args)) {
(*methods)[name] = std::make_pair((void (EmptyClass::*)())func, sizeof...(Args));
}

template<typename R, typename C, typename... Args>
R exec(C* obj, std::string name, Args... args) {
if(obj == nullptr)
throw "a tantrum";

auto& info = (*methods)[name];
auto g = info.first;
size_t i = info.second;
if(i > sizeof...(Args))
throw "a fit";

return switcher<R>(i, make_index_sequence<sizeof...(Args) + 1>{}, g, *obj, args...);
}

Is there any way to access everything except the last template parameter?

You could go with the standard method of using std::index_sequence

template<template<auto...> typename Tmp, size_t... Is, typename... Args>
constexpr auto take_as(std::index_sequence<Is...>, Args...)
{
using Tup = std::tuple<Args...>;
return Tmp<std::tuple_element_t<Is, Tup>{}...>{};
}

template<auto... Vals>
struct except_last
{
template<template<auto...> typename Tmp>
using as = decltype(take_as<Tmp>(std::make_index_sequence<sizeof...(Vals) - 1>{},
std::integral_constant<decltype(Vals), Vals>{}...));
};

Which you use as

using F = except_last<1, 2, 3, 4>::as<Foo>;  // F is Foo<1, 2, 3>

This is both easier to implement and read, but you potentially get O(n) instantiation depth. If you are obsessed with efficiency, you could do O(1) instantiation depth by abusing fold expressions

template<typename T>
struct tag
{
using type = T;
};

template<typename F, typename... Ts>
using fold_t = decltype((F{} + ... + tag<Ts>{}));

template<size_t N, typename... Ts>
struct take
{
template<typename T>
auto operator+(tag<T>) -> take<N - 1, Ts..., T>;
};

template<typename... Ts>
struct take<0, Ts...>
{
template<template<auto...> typename Tmp>
using as = Tmp<Ts{}...>;

template<typename T>
auto operator+(tag<T>) -> take<0, Ts...>;
};

template<auto... Vals>
struct except_last
{
template<template<auto...> typename Tmp>
using as = fold_t<take<sizeof...(Vals) - 1>,
std::integral_constant<decltype(Vals), Vals>...>::template as<Tmp>;
};

how to return the last type of a variadic template?

You could do a template recursion as below:

template<typename T, typename... Ts>
struct LastTypeOfTs {
typedef typename LastTypeOfTs<Ts...>::type type;
};

template<typename T>
struct LastTypeOfTs<T> {
typedef T type;
};

template<typename... Ts>
typename LastTypeOfTs<Ts...>::type f() {
//...
}

LIVE DEMO

How to overload variadic templates when they're not the last argument

When a parameter pack doesn't appear last in the parameter declaration, it is a non-deduced context. A non-deduced context means that the template arguments have to be given explicitly. This is why foo #1 is a better overload. You can force the second overload call by providing explicit arguments (foo<int,int>(1,2,3)) or as you said, move the int to the front.

To make things clear, you can overload a function with variadic templates, but when they do not appear as the last argument, they cannot be deduced, which automatically disqualifies them as candidates when explicit arguments are not provided. When they are provided, the template parameters are replaced with their provided types and the resulting non-template function is a candidate in overload resolution.

To answer your question, you can put all arguments into a tuple and pick out the last and test that one. Then pass on an overload based on a simple is_same check:

template<class...Us>
void foo_impl(true_type,Us...); // last argument is int
template<class...Us>
void foo_impl(false_type,Us...); // last argument non-int

template<class...Us>
void foo( Us&&...us ) {
using tuple=tuple<Us...>;
using last=decltype(get<sizeof...(Us)-1>(declval<tuple>()));
foo_impl(is_same<decay_t<last>,int>{}, forward<Us>(us)...);
}

Obtain all-but-last parameter of variadic template

Do you like tuples?

How do you like forward as tuple?

struct foo {
template<class...Ts>
foo(Ts&&...ts):
foo(
magic<0>{}, // sent it to the right ctor
std::index_sequence< sizeof...(ts)-1 >{}, // the last shall be first
std::make_index_sequence<sizeof...(ts)-1>{}, // the first shall be last
std::forward_as_tuple(std::forward<Ts>(ts)...) // bundled args
)
{}
private:
template<size_t>
struct magic {};
template<size_t...I0s, size_t...I1s, class...Ts>
foo(
magic<0>, // tag
std::index_sequence<I0s...>, // first args
std::index_sequence<I1s...>, // last args
std::tuple<Ts...> args // all args
):
foo(
magic<1>{}, // dispatch to another tagged ctor
std::get<I0s>(std::move(args))..., // get first args
std::get<I1s>(std::move(args))... // and last args
)
{}
// this ctor gets the args in an easier to understand order:
template<class...Coords>
foo(magic<1>, std::vector<T> values, Coords...coords) {
}
};

here the public ctor packs up the arguments into a tuple of references (possibly l, possibly r). It also gets two sets of indexes.

It then sends it to magic<0> ctor, which shuffles the arguments around so that the last one is first (assuming the indexes are right).

The magic<1> ctor gets the vector first, then the coords.

Basically I shuffled the arguments around so the last one became first.

magic just exists to make me not have to think much about overload resolution, and make which ctor I'm forwarding to explicit. It would probably work without it, but I like tagging when I'm doing something crazy with ctor forwarding.



Related Topics



Leave a reply



Submit