Skip to content

std::transform To Convert Containers In Cpp

Almost all problems in the history of programming require some form of data sequence manipulation. With std::transform , you can convert containers into different data. In this post, we look at how to use this standard library function.

Teddy bear C++ transforming a pile of logs into a container house. Analogy to convert containers in C++
Figure 1: A very happy teddy bear transforming a pile of logs into a house.

Interestingly, this post is part of a series of C++ standard library posts, where we look into popular functions in the standard, read the documentation, and look at usage examples. If you like this post, check out how to use std::find_if to find elements in C++ containers.

Video Lesson – How std::transform Works In C++ With Examples

for the purposes of showing useful examples, the video below explains how to use std::transform to modify C++ vector, arrays, lists, maps or any other C++ container out there.

Interestingly, we look at how to transform an array of People information (including name, weight, etc) into a vector of BMIs (body mass index).

Purpose Of std::transform And Why You Should Use It In C++ To Convert Containers

Modifying or transforming big chunks of data is nothing new. In fact, all codebases I’ve worked with in the past required transforming vectors of a certain data type into another vector of a different type.

For this reason, std::trasnform is perfect for that specific problem: turning a container of a particular type into another container of a different type in C++.

For example, you may have an array of images that you want to pass through a fancy (perhaps machine learning) algorithm and deduce object labels. Therefore, the solution to this problem is obvious here. You want to transform your list of images into a list of labels.

And if it’s not yet clear, std::transform does just that.

Documentation & Usage

Before you use the function, you should probably take a look at the standard library’s transform documentation.

Specifically, you can notice that there are different ways you can call std::transform. The list below summarises the main calling conventions from the documentation page.

  • Transforming a single container into another. In this calling mode, std::transform traverses a single container and converts each element into a different type.
  • Transforming two containers into another container. Similarly, this calling mode makes std::transform traverse two containers jointly. The results of the conversion will still be added to a single container.

In addition, there are different signatures for each calling mode. For example, there are now constexpr alternatives to std::transform.

Using std::transform To Change C++ Vectors

In this section, we will look at the actual usage of std::transform in the context of modifying our own data types.

More specifically, consider the following struct and array in the code block below. Needless to say, we will be transforming the container below into something else.

struct Person
{
    float height;
    float weight;
    int age;
    std::string_view name;
};

static constexpr std::array<Person, 8> people {{
    {1.7f, 60.2f, 24, "matheus"},
    {1.9f, 99.1f, 22, "lucas"},
    {1.63f, 57.12f, 27, "linda"},
    {1.98f, 115.3f, 41, "kobe"},
    {1.82f, 75.5f, 41, "jake"},
    {1.65f, 52.2f, 37, "jena"},
    {1.78f, 110.3f, 57, "patrick"},
    {1.63f, 123.5f, 47, "drew"}
}};

In addition, the code above requires the headers <array> and <string_view> to work.

Transforming Our C++ Person Array Into An Array Of BMIs

Now that we’ve got the necessary context for our std::transform exercise, let’s put the function to use. Firstly, let’s create our BMI struct, which will be the type we will convert Person elements into.

struct PersonBmi
{
    float bmi;
    std::string_view name;
};

Assuming that the previously created structs and array were added to a C++ file, let’s write code to turn our Person array into a BMI in the main function.

int main()
{
  std::vector<PersonBmi> bmis;

  std::transform(begin(people),
    end(people),
    back_inserter(bmis),
    [](auto const& person){
      return PersonBmi{
        person.weight / (person.height * person.height),
        person.name
      };
    });
}

Voilà, you now have a vector of PersonBmi elements in bmis. Unexpectedly, each element in bmis corresponds to the same positional element in the people people array. Just note that you need to include <algorithm> to be able to run std::transform.

How Does Transform Work? What Types Of Arguments Does It Need? How Does It Convert Containers?

Unsurprisingly, since std::transform is a function that operates on containers, it requires iterators to be passed in.

If you don’t know what C++ iterators are, make sure you read about them online as they are an essential part of the standard library. However, they are objects that point to elements at a position in a C++ container. They can also represent ranges if two iterators refer to elements in the same container.

In simple terms, std::transform will go through each element in the input container, run a conversion function, then save the return type of the conversion into a destination array.

Input Arguments To std::transform

The list below describes what each argument is and what they represent.

  1. The first argument represents an input iterator. In other words, this is the iterator to the first element that std::transform will traverse. For most cases, you will probably want to use begin(container) that will get the first iterator of your container.
  2. On the other hand, the second argument represents the last element to traverse with std::transform. This is also an input iterator, which must refer to the same type as the first iterator. Note that std::transform will not include the element referenced by this iterator when traversing the container. Similarly, you will probably want to use end(container). Note that end(container) will get the element after the last element of container, meaning that std::transform will traverse the last element.
  3. The third argument is an output iterator. Specifically, this output iterator refers to where the conversion results will be stored, or the first position of the destination container. Given that most people will be transforming vectors, back_inserter(container) will get you the output iterator that will always insert at the back.
  4. Lastly, we have either a function pointer, a functor, or a lambda as the last argument. This is the conversion function that std::transform will execute on each element of the input array. Due to this behavior, this function object has to take in one argument of the same type as the input container. In addition, it must return the same type as the destination container!

Running std::transform With Two Different Input Containers In C++

Before we end the post, let’s also look at the other signature of std::transform. Specifically, the version of std::transform that takes two input arrays.

Let’s say you have two containers with the same number of elements. In addition, you want to combine or convert each corresponding element into a single value in a destination array.

For the purposes of this post, we will replicate Python’s zip function. If you’re a Python programmer, you know that zip takes two lists and turns them into one list, where each element is a pair of elements in the first list, combined with the corresponding elements in the second.

The code below replicates this function with the use of templates and std::transform‘s less-known two inputs overload.

template <typename T, typename U>
std::vector<std::pair<T, U>> zip(std::vector<T> const& A, std::vector<U> const& B)
{
  std::vector<std::pair<T, U>> results;

  std::transform(begin(A),
    end(A),
    begin(B),
    back_inserter(results),
    [](auto const& a, auto const& b){
        return std::make_pair(a, b);
    });

  return results;
}

Although the zip function above only works on vectors, calling it with two different vectors will return a vector where each element is a pair formed with the corresponding positional elements in the first and second input vectors.

Differences Between 1 vs 2 Input Containers

Essentially, the only differences are the extra input iterator (or begin(B) in the code above), and the extra argument that the conversion function has to take.

The extra input iterator is the first element on the second container. Interestingly, everything works fine if the two input containers have the same size. But what happens if they dont? Comment below and I’ll give you a shoutout if you get the right answer!

Similarly, the extra argument to the conversion function (or lambda in our case) is the element that comes from the additional container. Hence it must be the same type as the elements in our second container!

Advantages & Disadvantages Of Using std::transform To Convert Containers

There aren’t many problems with std::transform itself. However, using std::transform requires us to create the destination container in the first place, such as an empty vector to store our conversions into.

This is, in my opinion, not very elegant as we can’t const that output container, and hence make our code a little more prone to coding errors down the line. However, the alternatives with loops still require the same thing.

Personally, I prefer using std::transform when I can to make the intent of the code more visible. In addition, looking at a call to std::transform immediately tells you what is going on, in terms of what container you’re modifying, the conversion function, and where the results are stored. You just don’t get the same visual niceness with raw loops.


Have I missed anything? Do you have any questions or feedback? Feel free to comment below!

Published inCPP

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *