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_array
s in G. Sliepen's answer. However, I found that the existed version of recursive_transform
couldn't handle multi_array
s 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:
Which question it is a follow-up to?
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
What changes has been made in the code since last question?
Improving the capability of
recursive_transform
function withmulti_array
structure.Why a new review is being asked for?
All suggestions about this
recursive_transform
function are welcome.
1 Answer 1
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_array
s and const_sub_array
s.
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.
-
\$\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– JimmyHu2020年11月05日 02:38:56 +00:00Commented Nov 5, 2020 at 2:38
-
2\$\begingroup\$ @JimmyHu
requires (is_iterable(Container<Ts....>) && !is_element_iterable(Container<Ts...>))
\$\endgroup\$G. Sliepen– G. Sliepen2020年11月05日 07:16:39 +00:00Commented Nov 5, 2020 at 7:16