Alexei90
Alexei90

Reputation: 33

C++17 parameter pack expansion with function parameter evaluation

I'm playing around with variadic templates and fold expressions, in particular doing type conversions to put into function parameters. My understanding is that to do something like:

template<T, typename ... Args>
void convertAndEvaluate(const vector<T>& convertibles)
{
    size_t i = 0;
    evaluate(some_function<Args>(convertibles[i++])...);
}

would not work as the order of evaluation of the function inputs are unspecified. Fold expressions can give the right evaluation order, however their result is parentheses enclosed and cannot be used as a function input. I can achieve the same result with index_sequences through another templated function, but I was wondering if there was a more concise way with C++17, something like using constexpr with pack expansion.

Toy example:

#include <iostream>
#include <vector>
#include <utility>

using namespace std;

template<typename ... Args>
class Foo {
public:
    Foo() {}
    
    void just_print(const std::vector<int>& convertible)
    {
        size_t i = 0;
        ((cout << static_cast<Args>(convertible[i++]) << " "), ...);
        cout << endl;
    }

    template<typename T,T... ints>
    void expandEvaluate(const std::vector<int>& values, std::integer_sequence<T, ints...> int_seq)
    {
        eval(static_cast<Args>(values[ints])...);
    }
  
    void convert(const std::vector<int>& convertible)
    {
        expandEvaluate(convertible, std::make_index_sequence<sizeof...(Args)>());
    }
    
    void convert_wrong(const std::vector<int>& convertible)
    {
        size_t i = 0;
        eval(static_cast<Args>(convertible[i++])...);
    }
    
    void eval(const Args&... values)
    {
        ((cout << values << " "), ...);
        cout << endl;
    }
    
};


int main()
{
    
    Foo<double, int, float, int, double> bar;
    bar.eval(3, 4, 5, 6, 7);
    bar.just_print({3, 4, 5, 6, 7});
    bar.convert_wrong({3, 4, 5, 6, 7});
    bar.convert({3, 4, 5, 6, 7});
    return 0;
}

Output:

3 4 5 6 7                                                                                                                                                 
3 4 5 6 7                                                                                                                                                 
7 6 5 4 3                                                                                                                                                 
3 4 5 6 7

Edit: In retrospect, my solution with the integer expansion necessitates expanding two parameter packs simultaneously, is this defined in the standard?

Upvotes: 3

Views: 784

Answers (1)

max66
max66

Reputation: 66200

I think your solution (use of std::make_index_sequence/std::index_sequence to get indexes in right order) is a good one (and works also with C++14).

Starting from C++17 you can also use std::tuple/std::apply()

void convert2 (std::vector<int> const & cv)
 {
   std::size_t  i{};

   std::tuple  t{ static_cast<Args>(cv[i++])... };
   std::apply([=](auto ... args){ eval(args...); }, t);
 }

but it's almost your std::make_index_sequence/std::index_sequence solution, wrapped by std::apply().

Upvotes: 2

Related Questions