Matthias Pospiech
Matthias Pospiech

Reputation: 3494

When to prefer for-loop over std::transform or vice-versa

I would like to understand when it is more practical to use std::transform and when an old fashioned for-loop is better.

This is my code with a for loop, I want to combine two vectors into a complex one:

    vector<double> vAmplitude = this->amplitudeData(N);
    vector<double> vPhase = this->phaseData(N);
    vector<complex<double>,fftalloc<complex<double> > > vComplex(N);
    for (size_t i = 0; i < N; ++i)
    {
        vComplex[i] = std::polar(vAmplitude[i], vPhase[i]);
    }

This is my std::transform code

    vector<double> vAmplitude = this->amplitudeData(N);
    vector<double> vPhase = this->phaseData(N);
    vector<complex<double>,fftalloc<complex<double> > > vComplex;
    std::transform(
                begin(vPhase), end(vPhase), begin(vAmplitude),
                std::back_inserter(vComplex),
                [](double p, double a) { return std::polar(a, p); });

Note that vComplex is allocated without size, so I wonder when the allocations happends. Also I do not understand why, in the lambda expression, p and a must be reversed to their usage.

Upvotes: 18

Views: 9064

Answers (2)

Ami Tavory
Ami Tavory

Reputation: 76297

One consideration in favor of the standard algorithms, is that it prepares your code (and you) for the c++17 alternative execution model versions.

To borrow from JoachimPileborg's answer, say you write your code as

vector<complex<double>,fftalloc<complex<double> > > vComplex(N);
std::transform(
    begin(vAmplitude), end(vAmplitude), begin(vPhase),
    std::begin(vComplex),
    std::polar);

After some time, you realize that this is the bottleneck in your code, and you need to run it in parallel. So, in this case, all you'd need to do is add std::execution::par{} as the first parameter to std::transform. In the hand-rolled version, your (standard-compliant) parallelism choices are gone.

Upvotes: 18

Some programmer dude
Some programmer dude

Reputation: 409166

Regarding the allocation, that's what std::back_inserter does.

You could also set the size for the destination vector vComplex and use std::begin for it in the std::transform call:

vector<complex<double>,fftalloc<complex<double> > > vComplex(N);
std::transform(
            begin(vPhase), end(vPhase), begin(vAmplitude),
            std::begin(vComplex),
            [](double p, double a) { return std::polar(a, p); });

As for the reversal of the arguments in the lambda, it's because you use vPhase as the first container in the std::transform call. If you changed to use vAmplitude instead you could have passed just a pointer to std::polar instead:

std::transform(
            begin(vAmplitude), end(vAmplitude), begin(vPhase),
            std::begin(vComplex),
            std::polar);

Lastly as for when to call std::transform it's more of a personal matter in most cases. I personally prefers to use the standard algoritm functions before trying to do everything myself.

Upvotes: 3

Related Questions