Miguel Cantón
Miguel Cantón

Reputation: 1

Generate function parameters from a combination of initializer lists

I'm trying to automatize calling a function multiple times using a combination of parameters. Something like:

int sum3(int a, int b, int c) {
    return a + b + c;
}

std::vector<int> v = foo<sum3>({1, 2}, 0, {2, 3});

// Equivalent to:
// v.emplace_back(sum3(1, 0, 2));
// v.emplace_back(sum3(1, 0, 3));
// v.emplace_back(sum3(2, 0, 2));
// v.emplace_back(sum3(2, 0, 3));

Is something like this possible in C++20 using template metaprogramming?

Update I would be interested in nesting these expressions. For example:

int bar(int a) {
    return a + 1;
}

std::vector<int> v = foo<sum3>({1, 2}, foo<bar>({0, 1}), {2, 3});

// Equivalent to:
// std::vector<int> v = foo<sum3>({1, 2}, {1, 2}, {2, 3});

Upvotes: 0

Views: 91

Answers (2)

IlCapitano
IlCapitano

Reputation: 2094

In order to accept brace-enclosed initilizer lists, values and other collections, like vectors, you'll need a helper class. Something like:

template<typename T>
struct span_or_init_list
{
    span_or_init_list(std::initializer_list<T> list)
        : span(list)
    {}

    span_or_init_list(std::span<const T> span)
        : span(span)
    {}

    span_or_init_list(T const &val)
        : span(&val, 1)
    {}

    template<typename Range>
    span_or_init_list(Range const &v)
        : span(v)
    {}

    std::span<const T> span;
};

Unfortunately the compiler cannot deduce span_or_init_list<T> from something like {0, 1}, so you need to do something metaprogramming to write foo.

Since partial function template specialization is limited, a constexpr variable could be used instead:

template<auto func, typename FuncT = decltype(func)>
inline constexpr int foo;

template<auto func, typename Ret, typename ...Args>
std::vector<Ret> foo_impl(span_or_init_list<std::decay_t<Args>> ...list)
{
    // do work here
}

template<auto func, typename Ret, typename ...Args>
inline constexpr auto foo<func, Ret(*)(Args...)> = foo_impl<func, Ret, Args...>;

For the implementation of foo_impl you could for example use @康桓瑋's solution from their answer.

So in the end these calls work:

std::vector<int> v1 = foo<sum3>({1, 2}, 0, {2, 3});
std::vector<int> v2 = foo<sum3>({1, 2}, foo<abs>({0, 1}), {2, 3});

See full example on godbolt.org

Upvotes: 1

康桓瑋
康桓瑋

Reputation: 43156

This will much simpler with the help of range-v3

template<auto f, class... Ts>
auto foo(std::initializer_list<Ts>... il) {
  return ranges::views::cartesian_product(il...)
       | ranges::views::transform([](auto t) { 
           return std::apply([](auto... args) { return f(args...); }, t);
         })
       | ranges::to<std::vector>;
}

std::vector v = foo<sum3>({1, 2}, {0}, {2, 3});

where views::cartesian_product has tier 1 priority in the C++23 ranges plan, and ranges::to has been adopted by C++23.

Demo

Upvotes: 1

Related Questions