9

In this post one of the answers recommends changing a std::string case this way:

std::string str = "Hello World";
std::transform(str.begin(), str.end(),str.begin(), ::toupper);

I've used it and it works so far in Visual Studio 2010. But is it guaranteed by the standard to always work? My concern is that I can imagine the possibility of implementations where writing to the output iterator (the third argument) could invalidate the input iterators (arguments one and two).

So, in summary, is the above method safe and portable?

Community
  • 1
  • 1
John Fitzpatrick
  • 4,207
  • 7
  • 48
  • 71
  • 1
    The usage of the iterators is correct - but not the usage of `::toupper`. – nosid Oct 05 '13 at 17:26
  • @nosid: I saw in the comments of the post I got the sample from that `::toupper` could be a macro. I will have to address that. – John Fitzpatrick Oct 05 '13 at 17:39
  • 1
    It was trying to say: If you replace `toupper` with some function `char foobar(char)`, then your question can be directly answered. However, there are a lot of problems regarding `toupper`. Look for `unsigned char` in http://stackoverflow.com/questions/735204/convert-a-string-in-c-to-upper-case – nosid Oct 05 '13 at 18:09

3 Answers3

12

Yes, this is guaranteed to be safe (as long as operation itself doesn't modify the elements or invalidate iterators).
From chapter [alg.transform] from draft n3337:

template<class InputIterator, class OutputIterator,  
    class UnaryOperation>  
OutputIterator  
transform(InputIterator first, InputIterator last,  
    OutputIterator result, UnaryOperation op);  

template<class InputIterator1, class InputIterator2,  
    class OutputIterator, class BinaryOperation>  
OutputIterator  
transform(InputIterator1 first1, InputIterator1 last1,  
    InputIterator2 first2, OutputIterator result,  
    BinaryOperation binary_op);  

2 Requires: op and binary_op shall not invalidate iterators or subranges, or modify elements in the ranges [first1,last1], [first2,first2 + (last1 - first1)], and [result,result + (last1 -first1)].

[...]

5 Remarks: result may be equal to first in case of unary transform, or to first1 or first2 in case of binary transform.

SCFrench
  • 8,244
  • 2
  • 31
  • 61
jrok
  • 54,456
  • 9
  • 109
  • 141
3

If you look into the first possible implementation of std::transform

template<class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, 
                   UnaryOperation unary_op)
{
    while (first1 != last1) {
        *d_first++ = unary_op(*first1++);
    }
    return d_first;
}

It may appear that its not "safe".

However, with std::transform(str.begin(), str.end(),str.begin(), ::toupper);

d_first and first1 point to the same place, but they are not the same iterator !

There isn't any problem with incrementing both those iterators in a single statement.

Another implementation is some what like this (from MingW header file), which is equivalent, but looks bit cleaner

template<class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, 
                   UnaryOperation unary_op)
{

  for (; first1 != last1; ++first1, ++d_first)
    *d_first = unary_op(*first1);

    return d_first;
}

Edited thanks to John Bartholomew

P0W
  • 46,614
  • 9
  • 72
  • 119
2

Yes, you can use the input iterator as the output iterator also, on a modifying algorithm it just means the modification will be done inline (on the source container) rather than on some other destination container.

Duncan Smith
  • 530
  • 2
  • 10