# Chapter 21 - std::transform

## Preamble

The `std::transform` function is a very useful and versatile algorithm.&#x20;

In a nutshell, the algorithm allows one to **apply a function to each element in a range**.&#x20;

### Transforming as a concept&#x20;

Consider the case where we have a vector of doubles corresponding to a series of Celsius values that we want to convert into Fahrenheit values, we might employ code similar to the following:

```cpp
std::vector<float> tempInCelsius {31.2, 28.1, 3.1, 12.8};
std::vector<float> tempInFahrenheit; 

for (auto celsius: tempInCelsius) {
    float fahrenheit = celsius * (9 / 5) + 32; 
    tempInFahrenheit.push_back(fahrenheit);
}
```

Though it is often good to abstract the transformation logic applied to each element into its own function:

```cpp
float toFahrenheit(float celsius) {
    return celsius * (9 / 5) + 32; 
}

int main() {
    std::vector<float> tempInCelsius {31.2, 28.1, 3.1, 12.8};
    std::vector<float> tempInFahrenheit; 

    for (auto celsius: tempInCelsius) {
        tempInFahrenheit.push_back(toFahrenheit(celsius););
    }
}
```

In the above example, we take each element in `tempInCelsius`, apply the `toFahrenheit` function to it, then store the corresponding results in `tempInFahrenheit`.&#x20;

In other words, we "transformed" each element in one range into another:

![(credits: https://www.fluentcpp.com/2017/02/13/transform-central-algorithm/)](https://4122513583-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MSrqC8yBLK9rXNyJ6ol%2F-MeqMDHUyBixt97rDBDy%2F-MeqPbAE9mtidqH049pX%2Fimage.png?alt=media\&token=0cbbadf1-6bb3-4c1e-82ab-6e9e0a3c7c8b)

This concept is also referred to as **map**, or **apply-to-all** in other languages.&#x20;

## `std::transform`

The `std::transform` algorithm is exactly this idea - it allows us to apply a function to each element in a range, and store its results in a separate (or the same) range.&#x20;

### Usage&#x20;

The basic syntax of the `std::transform` is as follows:

```cpp
std::transform(
    <start of range>,
    <end of range>,
    <start of output>,
    <function to apply>
)
```

* The first two parameters define a range of elements to apply our function to
* The third parameter is an iterator that will be used to store the elements; the program will write to `*iter` and increment it for each element.&#x20;
* The fourth parameter is a function that will be applied to each element in the transformation range.&#x20;

### Example&#x20;

We can rewrite our original example of transforming Celsius to Fahrenheit values:

```cpp
float toFahrenheit(float celsius) {
    return celsius * (9 / 5) + 32; 
}

int main() {
    std::vector<float> tempInCelsius {31.2, 28.1, 3.1, 12.8};
    std::vector<float> tempInFahrenheit; 
    tempInFahrenheit.resize(tempInCelsius);

    std::transform(
        tempInCelsius.begin(),
        tempInCelsius.end(),
        tempInFahrenheit.begin(),
        toFahrenheit
    )
}
```

Functionally, the above code behaves exactly the same as the one we've originally written.&#x20;

{% hint style="warning" %}
Note that we have also resized the `tempInFahrenheit` vector in line 8.&#x20;

This is because when using the `std::transform` function we need to ensure that the output iterator has sufficient space to store the results.

If we want to *insert* into the sequence, we can use `std::back_inserter(tempInFahrenheit)` as the output iterator. This removes the need to resize the result vector beforehand.&#x20;
{% endhint %}

## On Two Ranges&#x20;

The `std::transform` function has a second overload that takes (in essence) two ranges, and applies a binary function that takes **two parameters**, on each **pair of elements** taken from the two ranges:

![(credits: https://www.fluentcpp.com/2017/02/13/transform-central-algorithm/)](https://4122513583-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MSrqC8yBLK9rXNyJ6ol%2F-MeqaUEnqzZNEW3-M2rU%2F-MeqkzwQZXF8FdfFrEFK%2Fimage.png?alt=media\&token=872c6893-aa4c-4907-bf96-ce32e7f200d0)

Here is an example that performs a pairwise multiplication addition between two vectors:

```cpp
int multiply(int a, int b) {
    return a * b;
}

int main() {
    std::vector<int> first {1, 2, 3, 4, 5};
    std::vector<int> second {5, 4, 3, 2, 1};
    std::vector<int> result;
    
    std::transform(
        first.begin(),
        first.end(),
        second.begin(),
        std::back_inserter(result),
        multiply
    );
}
```

As shown in the diagram, the above snippet will traverse the entirety of the first range, reads the counterpart from the second range, applies the `multiply` function with the elements from each range, and insert the results into the `results` vector.&#x20;

{% hint style="warning" %}
When using this overload, you have to be careful to ensure that the second range is **at least as long** as the first one.
{% endhint %}

This concept of performing a pairwise enumeration over two collections has the general name called **zip**.&#x20;

## Transforming In-Place&#x20;

In the above examples, when we transformed a range, we stored the resulting elements in a separate range.&#x20;

If no longer care about the original range, we can store the results directly back into the input range - this is called transforming **in-place**. &#x20;

```cpp
int main() {
    std::vector<float> temperatures {31.2, 28.1, 3.1, 12.8};

    std::transform(
        temperatures.begin(),
        temperatures.end(),
        temperatures.begin(),  // Store the results back into the input range 
        toFahrenheit
    )
}
```

## Why use `std::transform`?

At this point of your C++ journey, there will be little benefit to using `std::transform` over plain old for-loops, but the potential benefits that you will uncover later on cannot be understated.&#x20;

Nevertheless, the following are some of the reasons you might decide to start using `std::transform` *now*.

### Separating concerns of transforming and iterating&#x20;

With `std::transform`, it allows you to decompose a for-loop into a function that handles the transformation, and a function call that simply applies the transformation function to each element.&#x20;

### Using iterators ensures a generic interface&#x20;

Since the parameters for `std::transform` are iterators, you no longer have to care about **how** the elements should be inserted, as you would simply call `.begin()` to obtain an iterator for the output container, regardless of whether it is an `std::vector`, `std::list`, or `std::set`.&#x20;

If a for-loop was used instead, you would have to check whether to use the `.insert()` member function, or the `.push_back()` member function depending on the container used to store the result.&#x20;

### Different execution models&#x20;

With C++17, you will be able to specify different **execution models** for `std::transform` and similar algorithm function calls.

{% embed url="<https://en.cppreference.com/w/cpp/experimental/parallelism/existing#transform>" %}

This allows you to apply a transformation to a range in a **parallel fashion**, potentially speeding up your code immensely.&#x20;

This is beyond the scope of the course thus far - but good to keep in mind.&#x20;
