2
\$\begingroup\$

I have a function f(i, j) that accepts two std::size_t arguments and I want to make a constexpr std:array out of its values on a 2d grid, (i = 0, ..., rows-1; j = 0, ..., cols-1) in, say, column-major order.

The code:

#include <array>
#include <tuple>
#include <type_traits>

template<std::size_t rows, std::size_t cols>
constexpr auto make_2d_index_table()
{
    std::array<std::array<std::size_t, 2>, rows * cols> table{};
    for (std::size_t i = 0; i < table.size(); ++i)
        table[i] = {i % rows, i / rows};

    return table;
}

template<std::size_t rows, std::size_t cols, class Func>
constexpr std::array<std::invoke_result_t<Func, std::size_t, std::size_t>, rows * cols> 
    make_array_of_2d_func_values(Func&& func)
{
    return apply([&func](auto... row_col)
    {
        return std::array<std::invoke_result_t<Func, std::size_t, std::size_t>, rows * cols>
            {std::apply(std::forward<Func>(func), row_col)...};
    }, make_2d_index_table<rows, cols>());
}

Usage:

constexpr auto arr = make_array_of_2d_func_values<5, 3>(
    [](auto i, auto j) { return i + 2 * j; });

The object of the type returned by a function may not be default constructible. So I can't just create an array<invoke_result_t<...>, rows * cols> arr{} of default constructed objects and then initialize it in the for loop like in make_index_table().

Is this a good solution?

\$\endgroup\$
2
\$\begingroup\$

Instead of creating an array of tuples, it may be simpler to use std::make_index_sequence() to create an array of (1-dimensional) indexes, and decompose those into arrays and columns at the point where the function is actually called.

Doing that allowed me to eliminate the for loop:

#include <array>
#include <type_traits>
#include <utility>

template<std::size_t stride, class Func, std::size_t... i>
constexpr auto make_elements(Func&& func, std::index_sequence<i...>)
{
    using result_type = std::invoke_result_t<Func, std::size_t, std::size_t>;
    return std::array<result_type, sizeof...(i)>
        { func(i % stride, i / stride)... };
}

template<std::size_t rows, std::size_t cols, class Func>
constexpr auto make_array_alternative(Func&& func)
{
    return make_elements<rows>(std::forward<Func>(func),
                               std::make_index_sequence<rows*cols>());
}

This still involves two tightly-coupled functions. I managed to use std::apply() again to get it down to one function and a helper:

#include <array>
#include <tuple>
#include <type_traits>
#include <utility>

template<std::size_t... I>
constexpr auto
tuple_of_sequence(std::index_sequence<I...>)
{
    return std::make_tuple(I...);
}

template<std::size_t rows, std::size_t cols, class Func>
constexpr auto
make_array_alternative(Func&& func)
{
    using result_array = std::array<std::invoke_result_t<Func, std::size_t, std::size_t>, rows*cols>;
    return std::apply([&func](auto... i){ return result_array{func(i % rows, i / rows)...}; },
                      tuple_of_sequence(std::make_index_sequence<rows*cols>()));
}

In case it's useful, my test program to compare my alternative implementation with the original is:

#include <iostream>
int main()
{
    auto const f = [](auto row, auto col) { return row + 10 * col; };
    auto const print = [](auto arr) {
        for (auto i: arr)
            std::cout << i << ' ';
        std::cout << std::endl;
    };

    print(make_array_of_2d_func_values<5, 3>(f));
    print(make_array_alternative<5, 3>(f));
}
\$\endgroup\$
  • \$\begingroup\$ Thanks. I like your solution, it is much more clear. \$\endgroup\$ – Evg Dec 11 '17 at 16:26
3
\$\begingroup\$

One simple improvement: the return type of make_array_of_2d_func_values() can be auto, to save writing std::array<std::invoke_result_t<Func, std::size_t, std::size_t>, rows * cols> twice.

I think you may be able to specialize std::tuple_size and std::get for the index table so that it doesn't require any storage even when invoked with non-constexpr arguments; I'm still experimenting but expect an update to this answer!

\$\endgroup\$
  • \$\begingroup\$ In case it's not obvious - the "update" is posted as a new answer! \$\endgroup\$ – Toby Speight Sep 26 '18 at 12:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.