4
\$\begingroup\$

This is the follow-up question for A Summation Function For Various Type Arbitrary Nested Iterable Implementation in C++ and A Maximum Function For Various Type Arbitrary Nested Iterable Implementation in C++. In the summation and the maximum cases, the recursive technique is used for iterating all elements. The similar recursive structure is also used here. As the title mentioned, I am trying to implement a TransformAll function which can apply a function to arbitrary nested ranges. I know there is a std::transform function which can apply a function to a range and the applied range can be specified by first1, last1 parameters. I want to focus on the nested ranges here. The TransformAll function is with two input parameters, one is input ranges, the other is operation function object. The operation function object would be applied to all elements in the input range then return the result. The main implementation is devided into two types. The first one type as follow is the single iterable case, such as std::vector<long double>{ 1, 1, 1 }).

template<class T, class _Fn> requires Iterable<T>
static T TransformAll(const T _input, _Fn _Func);       //  Deal with the iterable case like "std::vector<long double>"

template<class T, class _Fn> requires Iterable<T>
static inline T TransformAll(const T _input, _Fn _Func)
{
    T returnObject = _input;

    std::transform(_input.begin(), _input.end(), returnObject.begin(), _Func);
    return returnObject;
}

The second one is to deal with the nested iterable case like std::vector<std::vector<long double>>.

template<class T, class _Fn> requires Iterable<T> && ElementIterable<T>
static T TransformAll(const T _input, _Fn _Func);

template<class T, class _Fn> requires Iterable<T> && ElementIterable<T>
static inline T TransformAll(const T _input, _Fn _Func)
{
    T returnObject = _input;
    std::transform(_input.begin(), _input.end(), returnObject.begin(),
        [_Func](auto element)->auto
        {
            return TransformAll(element, _Func);
        }
    );
    return returnObject;
}

The usage of the TransformAll:

std::vector<long double> testVector1;
testVector1.push_back(1);
testVector1.push_back(20);
testVector1.push_back(-100);
std::cout << TransformAll(testVector1, [](long double x)->long double { return x + 1; }).at(0) << std::endl;

std::vector<long double> testVector2;
testVector2.push_back(10);
testVector2.push_back(90);
testVector2.push_back(-30);

std::vector<std::vector<long double>> testVector3;
testVector3.push_back(testVector1);
testVector3.push_back(testVector2);
std::cout << TransformAll(testVector3, [](long double x)->long double { return x + 1; }).at(1).at(1) << std::endl;

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    A Summation Function For Various Type Arbitrary Nested Iterable Implementation in C++ and

    A Maximum Function For Various Type Arbitrary Nested Iterable Implementation in C++.

  • What changes has been made in the code since last question?

    The previous question focus on the summation and the maximum operation. The main idea in this question is trying to process all base elements in various nested ranges with a lambda function and remain the origin structure in output result.

  • Why a new review is being asked for?

    I think the design of this TransformAll function is more complex than the previous summation function case and maximum function case. The return value of the summation function case and the maximum function case is a single value. For keeping the origin structure here, the return type in each recursion epoch may be different. In my opinion about this code, there might be some problems existed. In the nested iterable case, is it a good idea about T returnObject = _input;? The size of returnObject is must the same as _input in order to work well with std::transform. Is there any better idea for allocate the size of this returnObject?

Oct 23, 2020 Update

The used Iterable and ElementIterable concepts are here.

template<typename T>
concept Iterable = requires(T x)
{
    x.begin();      // must have `x.begin()` 
    x.end();        // and `x.end()` 
};

template<typename T>
concept ElementIterable = requires(T x)
{
    x.begin()->begin();
    x.end()->end();
};
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Try to match the conventions of the STL

Your algorithms are getting more generic. Consider that you would want to use your algorithms in code that also uses regular STL algorithms. Then it is a bit annoying that you are using a different naming convention. At this point, I think it would be helpful to match the conventions of the STL. I would name the function recursive_transform(), since recursive is a bit more precise than all. Try to make it look like std::ranges::transform().

Your earlier implementations could also be made more generic; for example instead of having separte nested sum and nested max functions, make an algorithm that looks like std::reduce().

Avoid making copies of the input

In this part of the code:

std::transform(_input.begin(), _input.end(), returnObject.begin(),
    [_Func](auto element)->auto
    {
        return TransformAll(element, _Func);
    }
);

You are passing element by value. Since element might be a nested container, that means you are copying potentially a huge amount of memory. This should be auto &element.

Also note that the ->auto return type is redundant.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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