Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 900
Matthias Pospiech Avatar asked Aug 26 '16 20:08

Matthias Pospiech


2 Answers

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.

like image 176
Ami Tavory Avatar answered Sep 28 '22 02:09

Ami Tavory


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.

like image 36
Some programmer dude Avatar answered Sep 28 '22 02:09

Some programmer dude