3

Why std::transform doesn't work this way:

std::string tmp = "WELCOME";
std::string out = "";
std::transform(tmp.begin(), tmp.end(), out.begin(), ::tolower);

out is empty!

But this works:

std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower);

I don't want the transformation to happen in-place.

Xigma
  • 177
  • 1
  • 11

2 Answers2

5

You are writing in out-of-bounds memory, since the range of out is smaller than that of tmp. You can store the result in out by applying std::back_inserter.

As user17732522 pointed out, since it's not legal to take the adress of a standard libary function, it's better to pass over a lamda object that calls std::tolower on the character when needed.

std::transform(tmp.begin(), tmp.end(), std::back_inserter(out), [](auto c) {
    return std::tolower(static_cast<unsigned char>(c));
});
Stack Danny
  • 7,754
  • 2
  • 26
  • 55
  • 1
    [`std::tolower`](https://en.cppreference.com/w/cpp/string/byte/tolower) is even worst: *"the behavior of `std::tolower` is undefined if the argument's value is neither representable as `unsigned char` nor equal to EOF"*, some cast to `unsigned char`/ from `int` is required. – Jarod42 Nov 22 '22 at 15:36
  • 1
    @Jarod42 That applies to both `::tolower` and `std::tolower` and all of the functions from ``/``. It would be practically always wrong to call these functions without cast, but it seems that often the non-ASCII case is simply ignored. – user17732522 Nov 22 '22 at 17:35
  • 1
    @StackDanny If you want to have this be correct even for the non-ASCII characters, then `auto` or `char` will not do. The parameter has to have type `unsigned char` (or it must be cast to it before passing to `tolower`). I neglected that in my previous deleted comment. – user17732522 Nov 22 '22 at 17:41
  • 1
    I guess I have been using `tolower` recklessly all the time. I wonder why the decision was not to define for `std::string::value_type`, ergo `char`. – Stack Danny Nov 22 '22 at 17:52
3

One way is to resize the string to allocate sufficient memory for std::transform to populate it.

out.resize(tmp.size()); // <---
std::transform(tmp.begin(), tmp.end(), out.begin(), ::tolower);

Alternatively, as others have mentioned you could use std::back_inserter, which handles inserting for you. We can also call out.reserve to save time on allocations (especially if your string is large).

out.reserve(tmp.size()); // (optional) Smol optimisation.
std::transform(tmp.begin(), tmp.end(), std::back_inserter(out), ::tolower);
TrebledJ
  • 8,713
  • 7
  • 26
  • 48
  • 1
    This will usually work, but the behavior is still technically unspecified if a standard library function like `::tolower` is passed directly. A lambda calling the function should be passed instead. – user17732522 Nov 22 '22 at 15:28