1

I was trying to transform a string into lowercase and store it in another variable using std::transform and std::tolower. I first tried:

string str1("Hello");
string lowerStr1;
transform(str1.begin(), str1.end(), lowerStr1.begin(), ::tolower);
cout << lowerStr1 << endl;

But, lowerStr1 contained nothing. After initializing lowerStr1 with str1, I got the desired result. I want to know the intuition behind this. Could someone explain why lowerStr1 should be initialized in this case?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
hpark95
  • 13
  • 2
  • In addition to the problem you're struggling with, see [the notes here](https://en.cppreference.com/w/cpp/string/byte/tolower) on why you want to cast the argument for `tolower` to an `unsigned char`. It's a problem you probably won't encounter, but when you do it's an absolute mind if you don't know it's coming. – user4581301 Feb 21 '20 at 02:39
  • Thanks for the insight! – hpark95 Feb 21 '20 at 03:26

3 Answers3

1

lowerStr1 is empty, and std::transform won't insert elements into it.

std::transform applies the given function to a range and stores the result in another range, beginning at d_first.

You can use std::back_inserter, which constructs a std::back_insert_iterator, which would call push_back() on the container to insert elements.

transform(str1.begin(), str1.end(), back_inserter(lowerStr1), ::tolower);

Or make lowerStr1 containing 5 elements in advance.

string lowerStr1(5, '\0');
transform(str1.begin(), str1.end(), lowerStr1.begin(), ::tolower);

or

string lowerStr1;
lowerStr1.resize(5);
transform(str1.begin(), str1.end(), lowerStr1.begin(), ::tolower);

Could someone explain why lowerStr1 should be initialized in this case?

That's because you initialize lowerStr1 containing 5 elements in advance as above. What's the value of the initialized elements doens't matter in fact.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
0

This is because your call to std::transform is logically equivalent to the following code:

auto b=str1.begin();
auto e=str1.end();
auto p=lowerStr1.begin();

while (b != e)
{
       *p=tolower(*b);
       ++b;
       ++e;
}

But lowerStr1, is a completely empty string. lowerStr1.begin() gives you, loosely speaking, a pointer to an empty string. So writing to that pointer and, adding insult to injury, incrementing it and continuing to write to it, result in undefined behavior, memory corruption, and a non-trivial possibility of a crash.

You do not add content to an empty string by grabbing a pointer to it, and then scribbling into that pointer. There are several ways of doing that correctly, with push_back() or insert() methods. You can also use an iterator that does that, like a std::back_insert_iterator, which can use with std::transform.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
0

Generic algorithms won't change the size of the containers. You need to use an iterator adapter which implements operator= in a special way so that it actually insert elements. Therefore you can use back_inserter(lowerStr1) to make sure that lowerStr1 gets extended as trasform() does assignments.

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main() {
  string str1("Hello");
  string lowerStr1;
  transform(str1.begin(), str1.end(), std::back_inserter(lowerStr1), ::tolower);
  cout << lowerStr1 << endl;
}

aep
  • 1,583
  • 10
  • 18