Chapter 21 - std::transform
Preamble
The std::transform function is a very useful and versatile algorithm.
In a nutshell, the algorithm allows one to apply a function to each element in a range.
Transforming as a concept
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:
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:
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.
In other words, we "transformed" each element in one range into another:

This concept is also referred to as map, or apply-to-all in other languages.
std::transform
std::transformThe 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.
Usage
The basic syntax of the std::transform is as follows:
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
*iterand increment it for each element.The fourth parameter is a function that will be applied to each element in the transformation range.
Example
We can rewrite our original example of transforming Celsius to Fahrenheit values:
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.
Note that we have also resized the tempInFahrenheit vector in line 8.
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.
On Two Ranges
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:

Here is an example that performs a pairwise multiplication addition between two vectors:
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.
When using this overload, you have to be careful to ensure that the second range is at least as long as the first one.
This concept of performing a pairwise enumeration over two collections has the general name called zip.
Transforming In-Place
In the above examples, when we transformed a range, we stored the resulting elements in a separate range.
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.
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?
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.
Nevertheless, the following are some of the reasons you might decide to start using std::transform now.
Separating concerns of transforming and iterating
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.
Using iterators ensures a generic interface
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.
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.
Different execution models
With C++17, you will be able to specify different execution models for std::transform and similar algorithm function calls.
This allows you to apply a transformation to a range in a parallel fashion, potentially speeding up your code immensely.
This is beyond the scope of the course thus far - but good to keep in mind.
Last updated
Was this helpful?