Skip to main content
Update contents
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48

This is a follow-up question for A recursive_transform Template Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++. I am following the suggestions proposed by G. Sliepen in histhe answer. The techniques including passing a range to std::ranges::transform() and using std::invoke() are performed in the updated version recursive_transform implementation. On the other hand, recursive_depth function is used here for handling incorrect unwrap levels more gracefully. For dealing with std::array type, another overload is proposed.

This is a follow-up question for A recursive_transform Template Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++. I am following the suggestions proposed by G. Sliepen in his answer. The techniques including passing a range to std::ranges::transform() and using std::invoke() are performed in the updated version recursive_transform implementation. On the other hand, recursive_depth function is used here for handling incorrect unwrap levels more gracefully. For dealing with std::array type, another overload is proposed.

This is a follow-up question for A recursive_transform Template Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++. I am following the suggestions proposed by G. Sliepen in the answer. The techniques including passing a range to std::ranges::transform() and using std::invoke() are performed in the updated version recursive_transform implementation. On the other hand, recursive_depth function is used here for handling incorrect unwrap levels more gracefully. For dealing with std::array type, another overload is proposed.

Update summary information
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48

A recursive_transform Template Function with Unwrap Level for std::array Implementation in C++

This is a follow-up question for A recursive_transform Template Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++. I am following the suggestions proposed by G. Sliepen in his answer. The techniques including passing a range to std::ranges::transform() and using std::invoke() are performed in the updated version recursive_transform implementation. On the other hand, recursive_depth function is used here for handling incorrect unwrap levels more gracefully. For dealing with std::array type, another overload is proposed.

The experimental implementation

The experimental implementation of recursive_transform function with the unwrap level parameter is as follows.

  • recursive_transform function implementation

    //  recursive_transform function implementation (the version with unwrap_level)
    template<std::size_t unwrap_level = 1, class T, class F>
    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()
        }
    }
    
    /* This overload of recursive_transform is to support std::array.
    */
    template< std::size_t unwrap_level = 1,
              template<class T, std::size_t> class Container,
              typename F,
              typename T,
              std::size_t N >
    requires is_iterable<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::transform( std::begin(input),
                        std::end(input),
                        std::begin(output),
                        [f](auto &x){ return std::invoke(f, x); }
                      );
    
        return output;
    }
    
  • recursive_depth helper function implementation

    //  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;
    }
    

Full Testing Code

The full testing code:

//  A recursive_transform Template Function with Unwrap Level for std::array Implementation in C++

#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 <ranges>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

template<class T>
concept is_iterable = requires(T x)
{
    *std::begin(x);
    std::end(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...>>&&
    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<typename F, typename T>
using recursive_invoke_result_t = typename recursive_invoke_result<F, T>::type;

//  recursive_transform implementation (the version with unwrap_level)
template<std::size_t unwrap_level = 1, class T, class F>
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()
    }
}

/* This overload of recursive_transform is to support std::array
 */
template< std::size_t unwrap_level = 1,
          template<class T, std::size_t> class Container,
          typename F,
          typename T,
          std::size_t N >
requires is_iterable<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::transform( std::begin(input),
                    std::end(input),
                    std::begin(output),
                    [f](auto &x){ return std::invoke(f, x); }
                );

    return output;
}

int main()
{
    //  non-nested input test, lambda function applied on input directly
    int test_number = 3;
    std::cout << "non-nested input test, lambda function applied on input directly: \n"
              << recursive_transform<0>(test_number, [](auto&& element) { return element + 1; }) << '\n';

    //  test with array container
    static constexpr std::size_t D = 3;
    auto test_array = std::array< double, D >{1, 2, 3};
    std::cout << "test with array container: \n"
              << recursive_transform<1>(test_array, [](auto&& element) { return element + 1; })[0] << '\n';

    //  nested input test, lambda function applied on input directly
    std::vector<int> test_vector = {
        1, 2, 3
    };
    std::cout << recursive_transform<0>(test_vector, [](auto element)
        {
            element.push_back(4);
            element.push_back(5);
            return element;
        }).size() << '\n';

    //  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_vector2 = {
        test_vector, test_vector, test_vector
    };

    auto recursive_transform_result2 = recursive_transform<2>(
        test_vector2,
        [](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';
    return 0;
}

The output of the test code above:

non-nested input test, lambda function applied on input directly: 
4
test with array container: 
2
5
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

A Godbolt link is here.

All suggestions are welcome.

The summary information: