This is a follow-up question for A recursive_transform_view Template Function which returns a view in C++. I am trying to revise the structure of recursive_transform
template function implementation in C++. As the suggestion mentioned in Davislor's answer, For dealing with dangling reference issue, the updated recursive_transform
template function implementation is posted here.
The experimental implementation
make_view
template function implementation:template< std::ranges::input_range Container, std::copy_constructible F> requires (std::ranges::view<Container>&& std::is_object_v<F>) constexpr auto make_view(const Container& input, const F& f) noexcept { return std::ranges::transform_view( std::views::all(input), [&f](const auto&& element) constexpr { return recursive_transform(element, f ); } ); } /* Override make_view to catch dangling references. A borrowed range is * safe from dangling.. */ template < std::ranges::input_range T, std::copy_constructible F> requires (!std::ranges::borrowed_range<T>) constexpr std::ranges::dangling make_view(T&& input, const F& f) noexcept { return std::ranges::dangling(); }
Full Testing Code
The full testing code:
// The usages of `make_view` Template Function in C++
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <complex>
#include <concepts>
#include <cstdlib>
#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);
};
template<typename T>
concept is_summable = requires(T x) { x + 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::regular_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::regular_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::regular_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::regular_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;
// https://codereview.stackexchange.com/a/253039/231235
template<template<class...> class Container = std::vector, std::size_t dim, class T>
constexpr auto n_dim_container_generator(T input, std::size_t times)
{
if constexpr (dim == 0)
{
return input;
}
else
{
return Container(times, n_dim_container_generator<Container, dim - 1, T>(input, times));
}
}
namespace UL // unwrap_level
{
template< std::ranges::input_range Container,
std::copy_constructible F>
requires (std::ranges::view<Container>&&
std::is_object_v<F>)
constexpr auto make_view(const Container& input, const F& f) noexcept
{
return std::ranges::transform_view(
std::views::all(input),
[&f](const auto&& element) constexpr { return recursive_transform(element, f ); } );
}
/* Override make_view to catch dangling references. A borrowed range is
* safe from dangling..
*/
template < std::ranges::input_range T,
std::copy_constructible F>
requires (!std::ranges::borrowed_range<T>)
constexpr std::ranges::dangling make_view(T&& input, const F& f) noexcept
{
return std::ranges::dangling();
}
// clone_empty_container template function implementation
template< std::ranges::input_range Container,
std::copy_constructible F>
requires (std::ranges::view<Container>&&
std::is_object_v<F>)
constexpr auto clone_empty_container(const Container& input, const F& f) noexcept
{
const auto view = make_view(input, f);
recursive_invoke_result_t<F, Container> output(std::span{input});
return output;
}
// recursive_transform template function implementation (the version with unwrap_level template parameter)
template< std::size_t unwrap_level = 1,
class T,
std::copy_constructible F>
requires (unwrap_level <= recursive_depth<T>()&& // handling incorrect unwrap levels more gracefully, https://codereview.stackexchange.com/a/283563/231235
std::ranges::view<T>&&
std::is_object_v<F>)
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)>&&
is_sized<decltype(input)>)
{
output.reserve(input.size());
std::ranges::transform(
input,
std::ranges::begin(output),
[&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
);
}
else
{
std::ranges::transform(
input,
std::inserter(output, std::ranges::end(output)),
[&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
);
}
return output;
}
else if constexpr(std::regular_invocable<F, T>)
{
return std::invoke(f, input);
}
else
{
static_assert(!std::regular_invocable<F, T>, "Uninvocable?");
}
}
/* 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(
input,
std::ranges::begin(output),
[&f](auto&& element){ return recursive_transform<unwrap_level - 1>(element, f); }
);
return output;
}
// recursive_transform function implementation (the version with unwrap_level, without using view)
template<std::size_t unwrap_level = 1, class T, class F>
requires (!std::ranges::view<T>)
constexpr auto recursive_transform(const T& input, const F& f)
{
if constexpr (unwrap_level > 0)
{
static_assert(unwrap_level <= recursive_depth<T>(),
"unwrap level higher than recursion depth of input"); // trying to handle incorrect unwrap levels more gracefully
recursive_invoke_result_t<F, T> output{};
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()
}
}
}
namespace NonUL
{
template< std::ranges::input_range Container,
std::copy_constructible F>
requires (std::ranges::view<Container>&&
std::is_object_v<F>)
constexpr auto make_view(const Container& input, const F& f) noexcept
{
return std::ranges::transform_view(
std::views::all(input),
[&f](const auto&& element) constexpr { return recursive_transform(element, f ); } );
}
/* Override make_view to catch dangling references. A borrowed range is
* safe from dangling..
*/
template <std::ranges::input_range T,
std::copy_constructible F>
requires (!std::ranges::borrowed_range<T>,
std::is_object_v<F>)
constexpr std::ranges::dangling make_view(T&& input, const F& f) noexcept
{
return std::ranges::dangling();
}
/* Base case of NonUL::recursive_transform template function
https://codereview.stackexchange.com/a/283581/231235
*/
template< typename T,
std::regular_invocable<T> F>
requires (std::copy_constructible<F>)
constexpr auto recursive_transform( const T& input, const F& f )
{
return std::invoke( f, input );
}
/* The recursive case of NonUL::recursive_transform template function
https://codereview.stackexchange.com/a/283581/231235
*/
template< std::ranges::input_range Container,
std::copy_constructible F>
requires (std::ranges::input_range<Container>&&
std::ranges::view<Container>&&
std::is_object_v<F>)
constexpr auto recursive_transform(const Container& input, const F& f)
{
const auto view = make_view(input, f);
recursive_invoke_result_t<F, Container> output( std::span{view} );
// One last sanity check.
if constexpr( is_sized<Container> && is_sized<recursive_invoke_result_t<F, Container>> )
{
assert( output.size() == input.size() );
}
return output;
}
/* The recursive case of NonUL::recursive_transform 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(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::ranges::begin(output),
[&f](auto&& element){ return recursive_transform(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 nonul_recursive_transform_output1 = NonUL::recursive_transform(test_number, [](auto&& element) { return element + 1; });
std::cout << "NonUL::recursive_transform function output: \n"
<< nonul_recursive_transform_output1 << "\n\n";
// 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 nonul_recursive_transform_output2 = NonUL::recursive_transform(test_array, [](int&& element) { return element + 1; });
std::cout << "NonUL::recursive_transform function output: \n";
for(int i = 0; i < nonul_recursive_transform_output2.size(); ++i)
{
std::cout << nonul_recursive_transform_output2[i] << " ";
}
std::cout << "\n\n";
// TODO: 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"
// << UL::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 ul_recursive_transform_output3 = UL::recursive_transform<0>(test_vector1, [](auto element)
{
element.push_back(4);
element.push_back(5);
return element;
});
std::cout << "UL::recursive_transform function output: \n";
for(int i = 0; i < ul_recursive_transform_output3.size(); ++i)
{
std::cout << ul_recursive_transform_output3[i] << " ";
}
std::cout << '\n';
std::vector<int> test_vector2 = {
1, 2, 3
};
// [A Issue to Be Addressed] Error from Clang:
// error: no matching function for call to '__begin'
/*
auto nonul_recursive_transform_output3 = NonUL::recursive_transform(test_vector2, [](int element)
{
return element;
});
std::cout << "NonUL::recursive_transform function output: \n";
for(int i = 0; i < nonul_recursive_transform_output3.size(); ++i)
{
std::cout << nonul_recursive_transform_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"
<< UL::recursive_transform<0>(test_priority_queue, [](auto element)
{
element.push(4);
element.push(5);
element.push(6);
return element;
}).top() << '\n';
}
int main()
{
tests();
return 0;
}
The output of the test code above:
non-nested input test, lambda function applied on input directly:
NonUL::recursive_transform function output:
4
test with non-nested std::array container:
NonUL::recursive_transform function output:
2 3 4
std::vector input test, lambda function applied on input directly:
UL::recursive_transform function output:
1 2 3 4 5
test with std::priority_queue container:
6
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
A recursive_transform_view Template Function which returns a view in C++
What changes has been made in the code since last question?
For dealing with dangling reference issue, the updated
recursive_transform
template function implementation is posted here.Why a new review is being asked for?
I am not sure I understand the previous suggestions correctly and both implementation and the corresponding tests are posted here. If there is any misunderstanding, please let me know.
1 Answer 1
This isn’t really very useful. What you want to do, in just about every case I can imagine, is create the transformed data structure. It meets the requirements of std::input_range
, and can be passed by reference to nearly every function that expects a view.
For the remaining cases where you really do want to pass around a small object representing a view, you can create the transformed structure and then take a view of it with std::ranges::views::all
. This will not be able to outlive the transformed object.
Where it might do you some good is when you only need one element of a large data structure under the transformation, and it would be wasteful to fill a copy of the entire data structure with transformed data you won’t need. But in that case, it’s more efficient to retrieve the element and apply the transformation function to it.
Explore related questions
See similar questions with these tags.
std::ranges::views::all
. \$\endgroup\$input
directly totransform_view
is fine. But what you actually want to do here is create the transformed structure, then create a view of it. And you don’t need to write that yourself; it’sstd::ranges::views::all
. \$\endgroup\$