2
\$\begingroup\$

This is a follow-up question for A recursive_transform Template Function Implementation with std::invocable concept in C++, A recursive_transform Template Function Implementation with std::invocable Concept and Execution Policy in C++, A recursive_transform for std::vector with various return type and A recursive_invoke_result_t template structure. Thanks to indi's answer. After checked the mentioned "completely different way", I am trying to implement another version recursive_transform function here. A recursive version std::invoke_result_t is used instead of std::indirect_result_t to determine the type of output structure. Therefore, the structure

using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;

turns into recursive_invoke_result_t<F, Range> output{};.

The implementation of recursive_invoke_result_t template structure is from HTNW's answer.

The experimental implementation

The experimental implementation is as below.

//  recursive_invoke_result_t implementation
//  from https://stackoverflow.com/a/65504127/6667035
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;

template <std::ranges::range Range>
constexpr auto get_output_iterator(Range& output)
{
    return std::inserter(output, std::ranges::end(output));
}

template <class T, std::invocable<T> F>
constexpr auto recursive_transform(const T& input, const F& f)
{
    return f(input);
}

template <
    std::ranges::input_range Range,
    class F>
requires (!std::invocable<F, Range>)
constexpr auto recursive_transform(const Range& input, const F& f)
{
    recursive_invoke_result_t<F, Range> output{};
    
    auto out = get_output_iterator(output);
    
    std::ranges::transform(
        input.cbegin(),
        input.cend(),
        out,
        [&f](auto&& element) { return recursive_transform(element, f); }
        );
    return output;
}

Test cases

Considering G. Sliepen's answer, the std::vector<std::string> test cases is added.

//  non-nested input test, lambda function applied on input directly
int test_number = 3;
std::cout << recursive_transform(test_number, [](int element) { return element + 1; }) << std::endl;

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

//  std::vector<int> -> std::vector<std::string>
auto recursive_transform_result = recursive_transform(
    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) << std::endl;                                  //  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(
        recursive_transform_result,
        [](std::string x) { return std::atoi(x.c_str()); }).at(0) + 1 << std::endl; //  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(
    test_vector2,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl;     // 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(
    test_deque,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result3.at(0) << std::endl;


//  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(
    test_deque2,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing

std::cout << "string: " + recursive_transform_result4.at(0).at(0) << std::endl;

//  std::list<int> -> std::list<std::string>
std::list<int> test_list = { 1, 2, 3, 4 };
auto recursive_transform_result7 = recursive_transform(
    test_list,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing
std::cout << "string: " + recursive_transform_result7.front() << std::endl;


//  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_result8 = recursive_transform(
    test_list2,
    [](int x)->std::string { return std::to_string(x); });                          //  For testing
std::cout << "string: " + recursive_transform_result8.front().front() << std::endl;

A Godbolt link is here.

All suggestions are welcome.

The summary information:

\$\endgroup\$
2
\$\begingroup\$

Just some minor things:

Use std::ranges::begin(), end() and size()

Make sure you consistently use the free-standing functions, instead of using member functions .begin(), .end(), their const counterparts, and .size().

Remove get_output_iterator()

This looks like a utility function, but it is used only once, and it kind of hides what it is actually calling. Instead of using this function, I would call std::inserter() directly where needed:

... recursive_transform(const Range& input, const F& f)
{
    recursive_invoke_result_t<F, Range> output{};
    std::ranges::transform(
        std::ranges::begin(input),
        std::ranges::end(input),
        std::inserter(output, std::ranges::end(output)),
        [&f](auto&& element) { return recursive_transform(element, f); }
    );
    return output;
}

Make use of automatic type deduction

In your test cases, I see several opportunities where you can use automatic type deduction to save yourself some typing. In particular, most STL containers can deduce their type from the constructor, and the lambda's you pass to recursive_transform() also don't need an explicit return type. For example, you could write:

std::vector test_vector2 = {
    test_vector, test_vector, test_vector
};

auto recursive_transform_result2 = recursive_transform(
    test_vector2,
    [](int x) { return std::to_string(x); }
);
```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.