Home > Net >  How to match callable objects out of parameter pack to corresponding element in an array as argument
How to match callable objects out of parameter pack to corresponding element in an array as argument

Time:09-09

For example, say I have the following:

template<typename ...FunctionTypes>
static void MainFunction(FunctionTypes... functions) 
{
    constexpr Uint32_t NumFunctions= sizeof...(FunctionTypes);

    std::array<double, NumFunctions> myArray;
    double arg1 = 4.2;
    int arg2= 9;
    for_each_tuple(myArray, FigureOutThisPart(myFunctions, arg1, arg2)...);
}

Where for_each_tuple takes a tuple or array of size N as well as N functions to apply to each tuple entry. This part was tricky to implement, but works! It is defined as such:

namespace detail {
template <typename Tuple, std::size_t ...Indices, typename ...FunctionTypes>
constexpr void for_each_tuple_impl(Tuple&& tuple, std::index_sequence<Indices...>, FunctionTypes&&... functionsIn) {
    std::tuple<FunctionTypes...> functions = std::tie(functionsIn...);
    using swallow = int[];
    (void)swallow{
        1, // Make sure array has at least one element
        (std::get<Indices>(functions)(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})...
    };
}
}
template <typename Tuple, typename ...Functions>
void for_each_tuple(Tuple&& tuple, Functions&&... f) {
    constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
    static_assert(N == sizeof...(Functions), "Need one function per tuple entry");
    detail::for_each_tuple_impl(
        std::forward<Tuple>(tuple),
        std::make_index_sequence<N>{},
        std::forward<Functions>(f)...);
}

The idea is that I have set of myFunctions, each of which is operating on a different entry of myArray. Each function in myFunctions would take arg1, arg2, and the current array entry as arguments.

My goal is to be able to pass arg1 and arg2 into each of myFunctions so that those values can be used in the operation on the current array entry. Is there a sane way in which this can be done? Ideally, I would like the solution to be a constexpr so everything can get resolved at compile time.

CodePudding user response:

I think something like this could work:

#include <array>
#include <functional>

template<typename ...FunctionTypes>
constexpr void MainFunction(FunctionTypes... functions) 
{
    constexpr auto NumFunctions= sizeof...(FunctionTypes);

    std::array<double, NumFunctions> myArray{};//Zero-init for now.
    double arg1 = 4.2;
    int arg2= 9;

    std::size_t i=0;
    (std::invoke(functions, arg1,arg2, myArray[i  ]),...);
}

#include <iostream>

void foo(double a1, int a2, int a){
    std::cout<<"Called foo("<<a1<<','<<a2<<','<<a<<")\n";
}

int main()
{
    MainFunction(foo,foo);
    return 0;
}

It requires C 17 for the fold expression and the sequence points for the comma.

It can be evaluated at compile-time:

constexpr void bar(double a1, int a2, int a){
}
int main()
{
    constexpr auto x = (MainFunction(bar,bar),1); // Force compile-time evaluation.
    return 0;
}

CodePudding user response:

I would just make a tuple out of functors at very beginning and match an element of the tuple to a corresponding element of the array:

template<std::size_t I, typename E, typename ...F, typename ...Args>
constexpr void apply(const std::array<E, sizeof...(F)>& elements,
                     const std::tuple<F...>& functions,
                     Args... args) {
    std::get<I>(functions)(std::get<I>(elements), args...);
}

template<std::size_t ...I, typename E, typename ...F, typename ...Args>
constexpr void for_each(std::index_sequence<I...>,
                        const std::array<E, sizeof...(I)>& elements,
                        const std::tuple<F...>& functions,
                        Args... args) {
    (apply<I>(elements, functions, args...),...);
}


template<typename ...F>
constexpr static void MainFunction(F... functors)
{
    constexpr std::size_t size = sizeof...(F);
    constexpr std::array<double, size> elements{};
    
    constexpr double arg1 = 4.2;
    constexpr int arg2 = 9;
    
    for_each(std::make_index_sequence<size>(),
             elements,
             std::forward_as_tuple(functors...),
             arg1, arg2);
    
}

The client code will look something like that:

void function(double val, double arg1, int arg2) {
    std::cout << arg1   arg2   8 * val << std::endl;
}


int main() {
    MainFunction(function, [](double val, double arg1, double arg2) {
        std::cout << val / arg1   arg2 << std::endl;
    });
    return 0;
}

As long as argument functions are constexpr, the implementation is also constexpr:

template<typename ...F>
constexpr static auto MainFunction(F... functors) { ... return elements;  }
constexpr void function(double val, double arg1, int arg2) { ... }

int main() {
    // Retrieves the constexpr elements array from the function
    constexpr auto val = MainFunction(function, [](double, double, int){ ... });
}

CodePudding user response:

You can use std::bind_front to construct a function that will have placeholder arguments, which is a solution a little closer to what you originally intended. This, however, has a drawback of not being runnable in constexpr context:

template <std::size_t N, typename T, typename... Functions>
auto for_each_tuple(std::array<T, N> const& array, Functions... functions) {
    auto index = 0uz;
    (std::invoke(functions, array[index  ]), ...);
}

template<typename ...FunctionTypes>
static void MainFunction(FunctionTypes... functions)
{
    constexpr std::uint32_t NumFunctions= sizeof...(FunctionTypes);

    std::array<double, NumFunctions> myArray{};
    double arg1 = 4.2;
    int arg2= 9;
    for_each_tuple(myArray, std::bind_front(functions, arg1, arg2)...);
}

auto main() -> int {
    auto print_thrice = [](double d1, int i2, double arr) {
        std::cout << d1 << ' ' << i2 << ' ' << arr << '\n';
    };

    MainFunction(print_thrice, print_thrice, print_thrice);
}
  • Related