This is a follow-up question for A recursive_transform Template Function with Unwrap Level for std::array Implementation in C++. Considering the suggestion mentioned in Davislor's answer, I am trying to implement recursive_transform_view
template function and comparing it with recursive_transform
template function. The proposed experimental implementation is tested with int
(non-nested input test), non-nested std::array
, std::vector
container. Please noticed that without specifying unwrap_level
, generic lambda (auto&&
or auto
) cannot be used.
The experimental implementation
The experimental implementations of recursive_transform_view
template function is as follows.
recursive_transform_view
function implementation:/* Base case of recursive_transform_view template function https://codereview.stackexchange.com/a/283581/231235 */ template< typename T, std::invocable<T> F > requires (std::copy_constructible<F>) constexpr auto recursive_transform_view( const T& input, const F& f ) { return std::invoke( f, input ); } /* The recursive case of recursive_transform_view template function https://codereview.stackexchange.com/a/283581/231235 */ template< template<typename...> typename Container, std::copy_constructible F, typename... Ts > requires (std::ranges::input_range<Container<Ts...>>&& std::ranges::view<Container<Ts...>>) constexpr auto recursive_transform_view(const Container<Ts...>& input, const F& f) { const auto view = std::ranges::transform_view( std::ranges::subrange( std::begin(input), std::end(input) ), [f](const auto& x) constexpr { return recursive_transform_view( x, f ); } ); recursive_invoke_result_t<F, Container<Ts...>> output( view.begin(), view.end() ); // One last sanity check. if constexpr( is_sized<Container<Ts...>> && is_sized<recursive_invoke_result_t<F, Container<Ts...>>> ) { assert( output.size() == input.size() ); } return output; } /* The recursive case of recursive_transform_view template function for std::array https://codereview.stackexchange.com/a/283581/231235 */ template< template<typename, std::size_t> typename Container, typename T, std::size_t N, std::copy_constructible F> requires std::ranges::input_range<Container<T, N>> constexpr auto recursive_transform_view(const Container<T, N>& input, const F& f) { Container<recursive_invoke_result_t<F, T>, N> output; std::ranges::transform( // Use std::ranges::transform() for std::arrays input, std::begin(output), [&f](auto&& element){ return recursive_transform_view(element, f); } ); // One last sanity check. if constexpr( is_sized<Container<T, N>> && is_sized<recursive_invoke_result_t<F, Container<T, N>>> ) { assert( output.size() == input.size() ); } return output; }
Full Testing Code
The full testing code:
// A recursive_transform_view Template Function Implementation
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <complex>
#include <concepts>
#include <deque>
#include <execution>
#include <exception>
#include <functional>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <mutex>
#include <numeric>
#include <optional>
#include <queue>
#include <ranges>
#include <stack>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
// is_reservable concept
template<class T>
concept is_reservable = requires(T input)
{
input.reserve(1);
};
// is_sized concept, https://codereview.stackexchange.com/a/283581/231235
template<class T>
concept is_sized = requires(T x)
{
std::size(x);
};
// recursive_depth function implementation
template<typename T>
constexpr std::size_t recursive_depth()
{
return 0;
}
template<std::ranges::input_range Range>
constexpr std::size_t recursive_depth()
{
return recursive_depth<std::ranges::range_value_t<Range>>() + 1;
}
// recursive_invoke_result_t implementation
template<typename, typename>
struct recursive_invoke_result { };
template<typename T, std::invocable<T> F>
struct recursive_invoke_result<F, T> { using type = std::invoke_result_t<F, T>; };
template<typename F, template<typename...> typename Container, typename... Ts>
requires (
!std::invocable<F, Container<Ts...>>&& // F cannot be invoked to Container<Ts...> directly
std::ranges::input_range<Container<Ts...>>&&
requires { typename recursive_invoke_result<F, std::ranges::range_value_t<Container<Ts...>>>::type; })
struct recursive_invoke_result<F, Container<Ts...>>
{
using type = Container<
typename recursive_invoke_result<
F,
std::ranges::range_value_t<Container<Ts...>>
>::type
>;
};
template<template<typename, std::size_t> typename Container,
typename T,
std::size_t N,
std::invocable<Container<T, N>> F>
struct recursive_invoke_result<F, Container<T, N>>
{
using type = std::invoke_result_t<F, Container<T, N>>;
};
template<template<typename, std::size_t> typename Container,
typename T,
std::size_t N,
typename F>
requires (
!std::invocable<F, Container<T, N>>&& // F cannot be invoked to Container<Ts...> directly
requires { typename recursive_invoke_result<F, std::ranges::range_value_t<Container<T, N>>>::type; })
struct recursive_invoke_result<F, Container<T, N>>
{
using type = Container<
typename recursive_invoke_result<
F,
std::ranges::range_value_t<Container<T, N>>
>::type
, N>;
};
template<typename F, typename T>
using recursive_invoke_result_t = typename recursive_invoke_result<F, T>::type;
// clone_empty_container template function implementation
template<class T, class F>
constexpr auto clone_empty_container(const T& input, const F& f)
{
recursive_invoke_result_t<F, T> output;
return output;
}
// recursive_transform template function implementation (the version with unwrap_level)
template<std::size_t unwrap_level = 1, class T, class F>
requires (unwrap_level <= recursive_depth<T>()) // handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
constexpr auto recursive_transform(const T& input, const F& f)
{
if constexpr (unwrap_level > 0)
{
auto output = clone_empty_container(input, f);
if constexpr (is_reservable<decltype(output)>) // reserve space
{
output.reserve(input.size());
}
std::ranges::transform(
input, // passing a range to std::ranges::transform()
std::inserter(output, std::ranges::end(output)),
[&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
);
return output;
}
else
{
return std::invoke(f, input); // use std::invoke()
}
}
/* This overload of recursive_transform is to support std::array
*/
template< std::size_t unwrap_level = 1,
template<class, std::size_t> class Container,
typename T,
std::size_t N,
typename F >
requires std::ranges::input_range<Container<T, N>>
constexpr auto recursive_transform(const Container<T, N>& input, const F& f)
{
Container<recursive_invoke_result_t<F, T>, N> output;
std::ranges::transform( // Use std::ranges::transform() for std::arrays as well
input,
std::begin(output),
[&f](auto&& element){ return recursive_transform<unwrap_level - 1>(element, f); }
);
return output;
}
/* Base case of recursive_transform_view template function
https://codereview.stackexchange.com/a/283581/231235
*/
template< typename T,
std::invocable<T> F >
requires (std::copy_constructible<F>)
constexpr auto recursive_transform_view( const T& input, const F& f )
{
return std::invoke( f, input );
}
/* The recursive case of recursive_transform_view template function
https://codereview.stackexchange.com/a/283581/231235
*/
template< template<typename...> typename Container,
std::copy_constructible F,
typename... Ts >
requires (std::ranges::input_range<Container<Ts...>>&&
std::ranges::view<Container<Ts...>>)
constexpr auto recursive_transform_view(const Container<Ts...>& input, const F& f)
{
const auto view = std::ranges::transform_view(
std::ranges::subrange( std::begin(input), std::end(input) ),
[f](const auto& x) constexpr { return recursive_transform_view( x, f ); } );
recursive_invoke_result_t<F, Container<Ts...>> output( view.begin(), view.end() );
// One last sanity check.
if constexpr( is_sized<Container<Ts...>> && is_sized<recursive_invoke_result_t<F, Container<Ts...>>> )
{
assert( output.size() == input.size() );
}
return output;
}
/* The recursive case of recursive_transform_view template function for std::array
https://codereview.stackexchange.com/a/283581/231235
*/
template< template<typename, std::size_t> typename Container,
typename T,
std::size_t N,
std::copy_constructible F>
requires std::ranges::input_range<Container<T, N>>
constexpr auto recursive_transform_view(const Container<T, N>& input, const F& f)
{
Container<recursive_invoke_result_t<F, T>, N> output;
std::ranges::transform( // Use std::ranges::transform() for std::arrays
input,
std::begin(output),
[&f](auto&& element){ return recursive_transform_view(element, f); }
);
// One last sanity check.
if constexpr( is_sized<Container<T, N>> && is_sized<recursive_invoke_result_t<F, Container<T, N>>> )
{
assert( output.size() == input.size() );
}
return output;
}
void tests()
{
// non-nested input test, lambda function applied on input directly
std::cout << "non-nested input test, lambda function applied on input directly:\n";
int test_number = 3;
auto recursive_transform_output1 = recursive_transform<0>(test_number, [](auto&& element) { return element + 1; });
std::cout << "recursive_transform function output: \n"
<< recursive_transform_output1 << '\n';
auto recursive_transform_view_output1 = recursive_transform_view(test_number, [](auto&& element) { return element + 1; });
std::cout << "recursive_transform_view function output: \n"
<< recursive_transform_view_output1 << "\n\n";
assert(recursive_transform_output1 == recursive_transform_view_output1);
// test with non-nested std::array container
std::cout << "test with non-nested std::array container:\n";
static constexpr std::size_t D = 3;
auto test_array = std::array< double, D >{1, 2, 3};
auto recursive_transform_output2 = recursive_transform<1>(test_array, [](auto&& element) { return element + 1; });
std::cout << "recursive_transform function output: \n";
for(int i = 0; i < recursive_transform_output2.size(); ++i)
{
std::cout << recursive_transform_output2[i] << " ";
}
std::cout << '\n';
// Without specifying unwrap_level, generic lambda (auto&& or auto) cannot be used.
// error: invalid operands to binary expression ('std::array<double, 3>' and 'int')
//auto recursive_transform_view_output2 = recursive_transform_view(test_array, [](auto&& element) { return element + 1; });
auto recursive_transform_view_output2 = recursive_transform_view(test_array, [](int&& element) { return element + 1; });
std::cout << "recursive_transform_view function output: \n";
for(int i = 0; i < recursive_transform_view_output2.size(); ++i)
{
std::cout << recursive_transform_view_output2[i] << " ";
}
std::cout << "\n\n";
// test with nested std::arrays
auto test_nested_array = std::array< decltype(test_array), D >{test_array, test_array, test_array};
//std::cout << "test with nested std::arrays: \n"
// << recursive_transform<2>(test_nested_array, [](auto&& element) { return element + 1; })[0][0] << '\n';
std::cout << "std::vector input test, lambda function applied on input directly:\n";
std::vector<int> test_vector1 = {
1, 2, 3
};
auto recursive_transform_output3 = recursive_transform<0>(test_vector1, [](auto element)
{
element.push_back(4);
element.push_back(5);
return element;
});
std::cout << "recursive_transform function output: \n";
for(int i = 0; i < recursive_transform_output3.size(); ++i)
{
std::cout << recursive_transform_output3[i] << " ";
}
std::cout << '\n';
std::vector<int> test_vector2 = {
1, 2, 3
};
// Error from Clang:
// error: no matching function for call to '__begin'
/*
auto recursive_transform_view_output3 = recursive_transform_view(test_vector2, [](int element)
{
return element;
});
std::cout << "recursive_transform_view function output: \n";
for(int i = 0; i < recursive_transform_view_output3.size(); ++i)
{
std::cout << recursive_transform_view_output3[i] << " ";
}
*/
std::cout << "\n\n";
std::vector<int> test_vector = {
1, 2, 3
};
auto test_priority_queue =
std::priority_queue<int>(std::ranges::begin(test_vector), std::ranges::end(test_vector));
std::cout << "test with std::priority_queue container: \n"
<< recursive_transform<0>(test_priority_queue, [](auto element)
{
element.push(4);
element.push(5);
element.push(6);
return element;
}).top() << '\n';
// Convert each int element in std::vector to std::string
// std::vector<int> -> std::vector<std::string>
auto recursive_transform_result = recursive_transform<1>(
test_vector,
[](int x)->std::string { return std::to_string(x); }
); // For testing
std::cout << "std::vector<int> -> std::vector<std::string>: " +
recursive_transform_result.at(0) << '\n'; // recursive_transform_result.at(0) is a std::string
// std::vector<string> -> std::vector<int>
std::cout << "std::vector<string> -> std::vector<int>: "
<< recursive_transform<1>(
recursive_transform_result,
[](std::string x) { return std::atoi(x.c_str()); }).at(0) + 1 << '\n'; // std::string element to int
// std::vector<std::vector<int>> -> std::vector<std::vector<std::string>>
std::vector<decltype(test_vector)> test_nested_vector = {
test_vector, test_vector, test_vector
};
auto recursive_transform_result2 = recursive_transform<2>(
test_nested_vector,
[](int x)->std::string { return std::to_string(x); }
); // For testing
std::cout << "string: " + recursive_transform_result2.at(0).at(0) << '\n'; // recursive_transform_result.at(0).at(0) is also a std::string
// std::deque<int> -> std::deque<std::string>
std::deque<int> test_deque;
test_deque.push_back(1);
test_deque.push_back(1);
test_deque.push_back(1);
auto recursive_transform_result3 = recursive_transform<1>(
test_deque,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result3.at(0) << '\n';
// std::deque<std::deque<int>> -> std::deque<std::deque<std::string>>
std::deque<decltype(test_deque)> test_deque2;
test_deque2.push_back(test_deque);
test_deque2.push_back(test_deque);
test_deque2.push_back(test_deque);
auto recursive_transform_result4 = recursive_transform<2>(
test_deque2,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result4.at(0).at(0) << '\n';
// std::list<int> -> std::list<std::string>
std::list<int> test_list = { 1, 2, 3, 4 };
auto recursive_transform_result5 = recursive_transform<1>(
test_list,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result5.front() << '\n';
// std::list<std::list<int>> -> std::list<std::list<std::string>>
std::list<std::list<int>> test_list2 = { test_list, test_list, test_list, test_list };
auto recursive_transform_result6 = recursive_transform<2>(
test_list2,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result6.front().front() << '\n';
}
int main()
{
tests();
return 0;
}
The output of the test code above:
non-nested input test, lambda function applied on input directly:
recursive_transform function output:
4
recursive_transform_view function output:
4
test with non-nested std::array container:
recursive_transform function output:
2 3 4
recursive_transform_view function output:
2 3 4
std::vector input test, lambda function applied on input directly:
recursive_transform function output:
1 2 3 4 5
test with std::priority_queue container:
6
std::vector<int> -> std::vector<std::string>: 1
std::vector<string> -> std::vector<int>: 2
string: 1
string: 1
string: 1
string: 1
string: 1
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
A recursive_transform Template Function with Unwrap Level for std::array Implementation in C++
What changes has been made in the code since last question?
I am trying to implement
recursive_transform_view
template function and comparing it withrecursive_transform
template function.Why a new review is being asked for?
Please review the experimental implementation of
recursive_transform_view
template function. If there is any misunderstanding, please let me know.
std::transform
withstd::begin(output)
as the destination is going to work for containers where you need to insert. When I tried this, I generated astd::ranges::transform_view
and constructedoutput
from itsbegin
andend
iterators. \$\endgroup\$F
need to be copy constructible? It’s only captured and passed by reference. But it should be invocable. And you should call.size()
on a container only if itis_sized
, within anif constexpr
block, to avoid a compile error if the type does not have that member function. \$\endgroup\$F
need to be copy constructible, please check en.cppreference.com/w/cpp/ranges/transform_view .std::copy_constructible
is used intransform_view
. \$\endgroup\$[[range.transform]
requiresF
to be copy-constructable because one of the constructors ofstd::transform_view<V, F>
takes a copy ofF
. And you do actually declarestd::invocable
in the base case, so it won’t match unless that does. Looks okay. \$\endgroup\$