0
\$\begingroup\$

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

Godbolt link

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.

asked Mar 11, 2023 at 21:07
\$\endgroup\$
2
  • 2
    \$\begingroup\$ You want std::ranges::views::all. \$\endgroup\$ Commented Mar 12, 2023 at 6:42
  • \$\begingroup\$ No. I meant, passing input directly to transform_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’s std::ranges::views::all. \$\endgroup\$ Commented Mar 12, 2023 at 19:38

1 Answer 1

1
\$\begingroup\$

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.

answered Mar 12, 2023 at 19:51
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.