8

I have a function f1()

template <typename... Args>
void f1(Args... args)
{
    // the implementation is just an example, I don't really need a complicated
    // way to sum numbers
    boost::fusion::vector<Args...> v(args...);
    std::cout << boost::fusion::accumulate(v, 0, [](auto i1, auto i2) { return i1 + i2; }) << std::endl;
}

I want to call it from a function f2(), but with a different last argument. Is there a simple approach? I tried a naive one

template <typename... Args>
struct CallHelper;

template <>
struct CallHelper<>
{
    template <typename... Args>
    static void Apply(Args... args) { f1(args...); }
};

template <typename A0>
struct CallHelper<A0>
{
    template <typename... Args>
    static void Apply(Args ...args, A0 a0)
    {
        // substitute 10 to the last argument
        CallHelper<>::Apply(args..., 10);
    }
};

template <typename Head, typename ...TailArgs>
struct CallHelper<Head, TailArgs...>
{
    template <typename... Args>
    static void Apply(Args... args, Head head, TailArgs ...tailArgs)
    {
        CallHelper<TailArgs...>::Apply(args..., head, tailArgs...);
    }
};

template <typename... Args>
void f2(Args... args)
{
    CallHelper<Args...>::Apply(args...);
}

Of course it doesn't work, because Head head is not the first argument. Maybe there is a way to make Head head a parameter pack as well? Or there is something else I can do?

max66
  • 65,235
  • 10
  • 71
  • 111
facetus
  • 1,091
  • 6
  • 20

4 Answers4

7

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
Sergey
  • 7,985
  • 4
  • 48
  • 80
  • Oh, this is how I can unpack a tuple. I didn't know that. Stackoverflow search gave much longer solutions. Thanks! – facetus Jun 20 '17 at 18:29
3

With the help of index sequences...

#include <utility>
#include <iostream>

template <typename ... Args>
void f1 (Args ... args)
 {
   using unused=int[];

   (void)unused { 0, (std::cout << args << ", ", 0)... };

   std::cout << std::endl;
 }

template <std::size_t>
struct getVal
 {
   template <typename T1, typename T2>
   T2 operator() (T1 const &, T2 const & t2)
    { return t2; }
 };

template <>
struct getVal<0U>
 {
   template <typename T1, typename T2>
   T1 operator() (T1 const & t1, T2 const &)
    { return t1; }
 };

template <std::size_t ... Is, typename ... Args>
void f2_helper (std::index_sequence<Is...> const &, Args const & ... args)
 { f1 ( getVal<sizeof...(Is)-Is-1U>()(10, args)... ); }

template <typename ... Args>
void f2 (Args ... args)
 { f2_helper(std::make_index_sequence<sizeof...(Args)>{}, args...); }

int main()
 {
   f1(1, 2L, 3.3, "ten"); // print 1, 2, 3.3, ten,
   f2(1, 2L, 3.3, "ten"); // print 1, 2, 3.3, 10,
 }

It's a C++14 solution (require std::index_sequence and std::make_index_sequence) but should be simple create substitutes for C++11, if you need they.

max66
  • 65,235
  • 10
  • 71
  • 111
0

Just to post what I mentioned in the comments

#include <utility>
#include <iostream>

template<bool b, typename T1, typename T2>
decltype(auto) replace_if(T1&& t1, T2&& t2)
{
    if constexpr(b)
        return std::forward<T1>(t1);
    else
        return std::forward<T2>(t2);
}

template<typename... Args>
void f1(Args&&... args)
{
    (std::cout << ... << args) << std::endl;
}

template<typename T, typename... Args, size_t... I>
decltype(auto) replace_last_impl(std::index_sequence<I...>, T&& t, Args&&... args)
{
    return f1(replace_if<sizeof...(Args) - 1 == I>(std::forward<T>(t), std::forward<Args>(args))...);
}

template<typename T, typename... Args>
decltype(auto) replace_last(T&& t, Args&&... args)
{
    return replace_last_impl(std::index_sequence_for<Args...>{}, std::forward<T>(t), std::forward<Args>(args)...);
}

int main()
{
    f1(1, 2, 3);  // 123
    replace_last("three", 1, 2, 3);  // 12three
}

The star of the show is replace_if, which is a pretty generic way of transforming a parameter pack.

Passer By
  • 19,325
  • 6
  • 49
  • 96
0

There are easy ways to do it involving writing multiple problem-specific functions just to solve this problem.

I don't like that.

So first I write some helper functions. nth takes an index and a bunch of arguments, and returns the nth one of them:

template<std::size_t I, class...Args>
decltype(auto) nth( Args&&... args ) {
  return std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...));
}

index_over and index_upto let you expand parameter packs of size_t's inline in your function. This gets rid of having to create helper functions just to do unpacking:

template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f)->decltype(auto) {
    return decltype(f)(f)( std::integral_constant< std::size_t, I >{} );
  };
}
template<std::size_t N>
auto index_upto( std::integral_constant< std::size_t, N > ={} ) {
  return index_over( std::make_index_sequence<N>{} );
}

Then, we write our f2:

template<class...Args>
void f2( Args&&... args ) {
  index_upto< sizeof...(args)-1 >()(
    [&](auto...Is) {
      f1( nth<Is>(std::forward<Args>(args)...)..., 10 );
    }
  )
}

and done.

This does generate quadradic amounts of unused references, which a good compiler can optimize away but takes time to do that (at compile-time).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524