2
\$\begingroup\$

This is a follow-up question for An Element-wise Increment and Decrement Operator For Boost.MultiArray in C++ and A recursive_transform Template Function for BoostMultiArray. I am trying to implement a ones function which can construct a new Boost.MultiArray object with the given dimension and assign each value to 1. This ones function is also based on recursive_transform template function. The value assignment task is handled by [](auto& x) { return 1; } lambda function which is passed into recursive_transform.

The main implementation of ones function is as below.

template<class T, std::size_t NumDims>
auto ones(boost::detail::multi_array::extent_gen<NumDims> size)
{
    boost::multi_array<T, NumDims> output(size);
    return recursive_transform(output, [](auto& x) { return 1; });
}

Just for convenience, the used recursive_transform template function can also be checked here.

template<typename T>
concept is_back_inserterable = requires(T x)
{
    std::back_inserter(x);
};

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

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

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

template<typename T>
concept is_element_visitable = requires(T x)
{
    std::visit([](auto) {}, *x.begin());
};

template<typename T>
concept is_summable = requires(T x) { x + x; };


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>
requires (is_back_inserterable<Container<Ts...>>&& is_iterable<Container<Ts...>> && !is_elements_iterable<Container<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_back_inserterable<Container<Ts...>> && 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>)
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;
}

The test of this ones function:

auto A = ones<double, 3>(boost::extents[3][4][2]);

typedef decltype(A)::index index;

std::cout << "A:" << std::endl;
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;

The whole experimental code can be checked at here.

All suggestions are welcome.

The summary information:

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Make it more generic

What if I want twos or threes? What if T is a std::string?

Consider avoiding recursion in this case

Since you only pass in the size of the outer multi_array to ones(), it doesn't make sense to call recursive_transform(). You can access all the elements of the multi_array directly, and initialize them with std::fill_n():

template<class T, std::size_t NumDims>
auto filled_multi_array(boost::detail::multi_array::extent_gen<NumDims> size, const T &value)
{
    boost::multi_array<T, NumDims> output(size);
    std::fill_n(output.data(), output.num_elements(), value);
    return output;
}

As a bonus, since you now pass a value to the function, it can deduce the type of it, so you no longer have to specify any template parameters:

auto A = filled_multi_array(boost::extents[3][4][2], 1.0);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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