2
\$\begingroup\$

This is a follow-up question for A Sine Template Function For Boost.MultiArray in C++, A recursive_transform for std::array with various return type, A recursive_transform for std::vector with various return type and A recursive_transform Function For Various Type Nested Iterable With std::variant Implementation in C++.

In the previous question A Sine Template Function For Boost.MultiArray in C++, there is a suggestion which is to use recursive_transform function to handle multi_arrays in G. Sliepen's answer. However, I found that the existed version of recursive_transform couldn't handle multi_arrays well. In other words, another version of recursive_transform function which can be capable of multi_array structure is needed. I am trying to implement this as below. First, some concepts including is_multi_array, is_sub_array and is_const_sub_array are created.

template<typename T>
concept is_multi_array = requires(T x)
{
    x.num_dimensions();
    x.shape();
    boost::multi_array(x);
};

template<typename T>
concept is_sub_array = requires(T x)
{
    x.num_dimensions();
    x.shape();
    boost::detail::multi_array::sub_array(x);
};

template<typename T>
concept is_const_sub_array = requires(T x)
{
    x.num_dimensions();
    x.shape();
    boost::detail::multi_array::const_sub_array(x);
};

The main part of the recursive_transform function:

template<class T, class F>
auto recursive_transform(const T& input, const F& f)
{
    return f(input);
}

template<class T, std::size_t S, class F>
auto recursive_transform(const std::array<T, S>& input, const F& f)
{
    using TransformedValueType = decltype(recursive_transform(*input.cbegin(), f));

    std::array<TransformedValueType, S> output;
    std::transform(input.cbegin(), input.cend(), output.begin(), 
        [f](auto& element)
        {
            return recursive_transform(element, f);
        }
    );
    return output;
}

template<template<class...> class Container, class Function, class... Ts>
// non-recursive version
auto recursive_transform(const Container<Ts...>& input, const Function& f)
{
    using TransformedValueType = decltype(f(*input.cbegin()));
    Container<TransformedValueType> output;
    std::transform(input.cbegin(), input.cend(), std::back_inserter(output), f);
    return output;
}

template<template<class...> class Container, class Function, class... Ts>
requires is_elements_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...>& input, const Function& f)
{
    using TransformedValueType = decltype(recursive_transform(*input.cbegin(), f));
    Container<TransformedValueType> output;

    std::transform(input.cbegin(), input.cend(), std::back_inserter(output),
        [&](auto& element)
        {
            return recursive_transform(element, f);
        }
    );
    return output;
}

template<class T, class F> requires (is_multi_array<T> || is_sub_array<T> || is_const_sub_array<T>)
auto recursive_transform(const T& input, const F& f)
{
    boost::multi_array output(input);
    for (decltype(+input.shape()[0]) i = 0; i < input.shape()[0]; i++)
    {
        output[i] = recursive_transform(input[i], f);
    }
    return output;
}

Moreover, the used is_elements_iterable concept:

template<typename T>
concept is_elements_iterable = requires(T x)
{
    std::begin(x)->begin();
    std::end(x)->end();
};

Finally, the test of this recursive_transform with Boost.MultiArray is as follows.

// Create a 3D array that is 3 x 4 x 2
typedef boost::multi_array<double, 3> array_type;
typedef array_type::index index;
array_type A(boost::extents[3][4][2]);

// Assign values to the elements
int values = 1;
for (index i = 0; i != 3; ++i)
    for (index j = 0; j != 4; ++j)
        for (index k = 0; k != 2; ++k)
            A[i][j][k] = values++;

for (index i = 0; i != 3; ++i)
    for (index j = 0; j != 4; ++j)
        for (index k = 0; k != 2; ++k)
            std::cout << A[i][j][k] << std::endl;

auto test_result = recursive_transform(A, [](auto& x) {return std::sin(x); });

for (index i = 0; i != 3; ++i)
    for (index j = 0; j != 4; ++j)
        for (index k = 0; k != 2; ++k)
            std::cout << test_result[i][j][k] << std::endl;

The whole test of this recursive_transform can be checked at here.

All suggestions are welcome.

The summary information:

\$\endgroup\$
1
\$\begingroup\$

Only one concept is necessary for boost::multi_array types

The concepts is_sub_array and is_const_sub_array are redundant, since is_multi_array will match the first two ones as well. That is because of:

boost::multi_array(x);

The constructor of boost::multi_array has overloads for sub_arrays and const_sub_arrays.

Restrict the overload for non-recursive containers

In this overload:

template<template<class...> class Container, class Function, class... Ts>
// non-recursive version
auto recursive_transform(const Container<Ts...>& input, const Function& f)
{
    ...

You should require that Container<Ts...> is a non-recursive container, otherwise it could match types that are not containers as well.

Make sure your concepts check everything that is used

I did mention this before, but I missed it myself in an earlier review when I gave an example for the is_iterable concept. But since you use std::back_inserter(), you should check for that in the relevant concepts as well, since not all containers support inserting to the back. If you don't check it in the concept, compilation will still fail but the error message will be very hard to understand.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for the answer. About the "Restrict the overload for non-recursive containers" part, is there any further example or hint can help me know how to do that? \$\endgroup\$
    – JimmyHu
    Nov 5 '20 at 2:38
  • 2
    \$\begingroup\$ @JimmyHu requires (is_iterable(Container<Ts....>) && !is_element_iterable(Container<Ts...>)) \$\endgroup\$
    – G. Sliepen
    Nov 5 '20 at 7:16

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.