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;
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
A recursive_transform Template Function Implementation with std::invocable concept in C++,
A recursive_transform for std::vector with various return type and
What changes has been made in the code since last question?
I am trying to implement another version
recursive_transform
function withrecursive_invoke_result_t
andstd::ranges::transform
here.Why a new review is being asked for?
A recursive version
std::invoke_result_t
is used instead of something likesomething_something_recursively_indirect_result_t
because I am not sure how to usestd::indirect_result_t
correctly. If the idea of usingstd::indirect_result_t
is still better, please let me know. On the other hand, if there is any good example ofstd::indirect_result_t
usage available, please also tell me.
1 Answer 1
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); }
);
```