devxlogo

Using the Transform() Algorithm to Change a String’s Case

Using the Transform() Algorithm to Change a String’s Case

ommand line interpreters, HTTP requests, and SMS messages are only a few of the applications in which different letter cases merely cause noise. To overcome this problem, such applications usually convert all strings to uppercase before any further processing.



Sadly, most of these apps use C-style strings and ad-hoc, in-house conversion routines that more often than not suffer from bugs, illegibility, and performance overhead. The std::string class provides more than a hundred member functions and overloaded operators. Yet, none of these functions transforms a string to uppercase or lowercase letters.



Use the STL transform() algorithm to change a string’s case easily.Converting the Hard Way
There are numerous ways to change the case of a string. A naive implementation might look like this:

 #include #include using namespace std;int main(){ string s="hello"; for (int j=0; j

Though functionally correct, this loop is a maintenance headache. To apply a different type of transformation to the string, say to convert it to lowercase or transliterate all characters to their Cyrillic equivalent, you'll have to rewrite the loop's body. To improve the design, separate the string transformation into two operations: one that iterates through the string's elements and one that actually transforms every element. You gain more flexibility by decoupling these operations and simplify future maintenance.

Step 1: Iteration
The transform() algorithm defined in is rather flexible. Not only does it separate between the iterations and transformation operations, it also allows you to transform only a portion of the string. In addition, you can store the result in a different destination, should you prefer to keep the original string intact. The transform() algorithm has two overloaded versions but we will use only the following one:

OutputIterator transform(InputIterator first,                         InputIterator last,                         OutputIterator result,                         UnaryOperation unary_op);

You can find an explanation about the different iterator categories here. The first and second arguments are iterators pointing to the beginning and the end of the sequence being transformed. The third argument is an iterator pointing to the beginning of the destination sequence. If you wish to overwrite the current string, result and first should have identical values.

Step 2: Transformation
The fourth argument is a unary operator. It can either be an address of a function that takes a single argument or a function object. STL algorithms don't really care whether a unary operator is a function object or an address because they merely append () to it and let the compiler takes care of the rest. This example uses the standard toupper() function declared in :

#include  // for toupper#include #include using namespace std;string s="hello";transform(s.begin(), s.end(), s.begin(), toupper);

Alas, the program above will not compile because the name 'toupper' is ambiguous. It can refer either to:

int std::toupper(int); // from 

or

template    charT std::toupper(charT, const locale&);// from   

Use an explicit cast to resolve the ambiguity:

std::transform(s.begin(), s.end(), s.begin(),                (int(*)(int)) toupper);

This will instruct the compiler to choose the right toupper().

Design Improvements
There are ways to further benefit from using transform(). Suppose you need to transform a string to lowercase rather than uppercase. You change the transform() call to:

std::transform(s.begin(),                s.end(),                std::back_inserter(s1),                std::tolower);

It's not much of an improvement compared to the original for loop, is it? To avoid intrusive code changes such as this, use an additional level of indirection. Instead of passing a function's name as an argument, use a pointer to a function. This way, you can decouple the transform() call from the customers' requirements. Furthermore, the use of a pointer enables you to postpone the function binding to runtime:

int (*pf)(int)=tolower; transform(s.begin(), s.end(), s.begin(), pf); //lowercase

Notice that you don't need to change the transform() call now if you wish to apply yet another transformation:

pf=tocyrillic; // just an exampletransform(s.begin(), s.end(), s.begin(), pf); // Cyrillic

Conclusions
If using transform() to convert a string to uppercase seems like overkill to you, you're probably right. The string transformation was a red herring, though. The point was to show how to use transform() to manipulate sequences in a generic fashion. By using transform(), transforming a sequence of integers to their negative or square root values is a cinch:

template  class negate{public: T operator()(T t) { return -t;}};int arr[]={1, 2, 3};transform(arr,           arr+sizeof(arr)/sizeof(int),           arr,           negate()); // arr = {-1, -2, -3}

devx-admin

Share the Post: