2

I need to define a function with template parameter pack with C++14.

The caller of the function makes sure that the size of Args... must be even, such as 2, 4, 6... And my function will pass them two by two to two functions.

template<typename F, typename F2, typename... Args>
void func(F f, F2 f2, Args&&... params) {
    using params_t = std::tuple<Args...>;
    auto tp = std::make_tuple(params...);
    for (std::size_t i = 0; i < sizeof...(Args); ++i) {
        f(std::get<i>(tp));
        f2(std::get<++i>(tp));
    }
}

// caller
func(f1, f2, "abc", 3, "def", "ppp");

This won't work because i is not a constant expression.

What could I do? Is it the right and the only way to iterate over a parameter pack with std::tuple?

Yves
  • 11,597
  • 17
  • 83
  • 180
  • https://stackoverflow.com/questions/7230621/how-can-i-iterate-over-a-packed-variadic-template-argument-list – nullqube Apr 24 '22 at 04:36

2 Answers2

3

To use a std::tuple to iterate over a parameter pack, you would usually use a std::index_sequence to introduce a new pack of indices, and use a fold expression to do the actual iteration. Something like this:

template<typename F, typename F2, typename... Args, std::size_t... I>
void func_impl(F& f, F2& f2, std::tuple<Args...> params, std::index_sequence<I...>) {
    // This would be a fold expression over a comma in C++17:
    /*
    (([&]{}(
        f(std::get<I*2>(params));
        f2(std::get<I*2+1>(params));
    )), ...);
    */
    // C++14 version
    using consume = int[];
    (void) consume{ 0, [&]{
        f(std::get<I*2>(params));
        f2(std::get<I*2+1>(params));
        return 0;
    }()... };
}

template<typename F, typename F2, typename... Args>
void func(F f, F2 f2, Args&&... args) {
    static_assert(sizeof...(args) % 2 == 0, "Must pass a multiple of 2 args to func");
    func_impl(
        f, f2,
        std::forward_as_tuple(std::forward<Args>(args)...),
        std::make_index_sequence<sizeof...(args) / 2>{}
    );
}

But you can also iterate with recursion, which might be easier in your case:

template<typename F, typename F2>
void func(F&& f, F2&& f2) {
    // base case: do nothing
}

template<typename F, typename F2, typename Arg1, typename Arg2, typename... Args>
void func(F&& f, F2&& f2, Arg1&& arg1, Arg2&& arg2, Args&&... args) {
    // recursive case
    f(arg1);
    f2(arg2);
    func(std::forward<F>(f), std::forward<F2>(f2), std::forward<Args>(args)...);
}
Artyer
  • 31,034
  • 3
  • 47
  • 75
0

Here is a sample of the the kind of things I do, with my 2011 limited c++ :
(without lambdas nor std::tuple)

#include <iostream>
using namespace std ;

static int count ()
    {
    return 0 ;
    }

template <class HEAD,class...TAIL> int count ( const HEAD & , const TAIL & ...tail )
    {
    return count( tail... )+1 ;
    }


template <class FONCTOR> int apply_fonctor ( const FONCTOR & )
    {
    return 0 ;
    }

template <class FONCTOR,class HEAD,class ...TAIL> int apply_fonctor ( const FONCTOR & f , const HEAD & head , const TAIL&...tail )
    {
    f( head ) ;
    return apply_fonctor( f,tail... )+1 ;
    }

struct Eggs 
    {
    int x ;
    Eggs ( int x )          : x(x)                                           {}
    Eggs ( const Eggs & e ) : x(e.x)                                         { cout << "### Eggs duplicated" ;}
    Eggs & operator= ( const Eggs & e )                                      { cout << "### Eggs copied" ; x = e.x ;}
    template<class OUT> friend OUT & operator<< ( OUT & o , const Eggs & e ) { o << "egg=" << e.x ; return o ;}
    };

struct Functor
    {
    void operator() ( int    p )       const { cout << " [" << p << "] " ;}
    void operator() ( bool   p )       const { cout << " [" << (p?"true":"false") << "] " ;}
    void operator() ( double p )       const { cout << " [" << p << "] " ;}
    void operator() ( const char * p ) const { cout << " [" << p << "] " ;}
    void operator() ( const Eggs & p ) const { cout << " [" << p << "] " ;}
    };

void main ()
    {       
    Eggs e(42) ;
    cout << "count = " << count() << endl ;
    cout << "count = " << count( "just one" ) << endl ;
    cout << "count = " << count( 42,"spam",3.14159,false,e,12345678901234567890ull ) << endl ;

    cout << "Calls of functor :" ;
    Functor f ;
    int nb = apply_fonctor( f,e,123,"bread",true,1.4142 ) ;
    cout << "\n applied to " << nb << endl ;
    //apply_fonctor( f,12345678901234567890ull ) ; // <--- won't commpile because Functor does not handle ULL
    }

There is a first sample performing a simple count of the parameters,
and a second sample demonstrating how to apply a functor on each parameter.

NB : Eventual Eggs copy is revealed by ### in the output.
--> there is none !

The output should be:

count = 1
count = 6
Calls of functor : [egg=42]  [123]  [bread]  [true]  [1.4142]
 applied to 5```
Captain'Flam
  • 479
  • 4
  • 12