Previous fileTop of DocumentContentsIndex pageNext file
Apache C++ Standard Library User's Guide

33.3 Manipulators with Parameters

Manipulators with parameters are more complex than those without because there are additional issues to consider. Before we explore these issues in detail and examine various techniques for implementing manipulators with parameters, let's take a look at one particular technique, the one that is used to implement standard manipulators such as std::setprecision(), std::setw(), etc.

33.3.1 The Standard Manipulators

This implementation of the standard iostreams uses a certain technique for implementing most standard manipulators with parameters: the manipulator type manipT is a function pointer type; the manipulator object is the function pointed to; and the associated function fmanipT is a non-member function.

The C++ standard informally refers to the manipulator type as smanip. The type itself is implementation-specified and may not be accessible to user code at all (i.e., it may be defined in some private namespace or called something entirely different); all you know is that it is returned by some of the standard manipulators. In this implementation, the type referred to as smanip is a class template:

A standard manipulator like std::setprecision() can be implemented as a non-member function returning an object of some implementation-specified type smanip:

The associated function fmanipT is a private function, let's call it sprec:

33.3.2 The Principle of Manipulators with Parameters

The previous section gave an example of a technique for implementing a manipulator with one parameter: the technique used to implement the standard manipulators in iostreams. However, this is not the only way to implement a manipulator with parameters. In this section, we examine other techniques. Although all explanations are in terms of manipulators with one parameter, it is easy to extend the techniques to manipulators with several parameters.

Let's start right at the beginning: what is a manipulator with a parameter?

A manipulator with a parameter is an object that can be inserted into or extracted from a stream in an expression like:

Manip(x) must be an object of type manipT, for which the shift operators are overloaded. Here's an example of the corresponding inserter:

With this inserter defined, the expression std::cout << Manip(x); is equivalent to a call to the shift operator sketched above; that is, operator<<(std::cout, Manip(x)). Note that the name of the operator<<() does not need to be qualified with the name of any namespace. Since the operator will typically be defined in the same namespace as one of its arguments, it is in those namespaces that the operator definition will be sought to resolve the call.

Assuming that a side effect is created by an associated function fmanipT, the manipulator must call the associated function with its respective argument(s). Hence it must store the associated function together with its argument(s) for a call from inside the shift operator.

The associated function fmanipT can be a static member or a namespace-scope function, or even a member function of type manipT, for example.

In the inserter above, we've assumed that the associated function fmanipT is a static or a non-member function, and that it takes exactly one argument. Generally, the manipulator type manipT might look like this:

Note that this is only a suggested manipulator, however. In principle, you can define the manipulator type in any way that makes the associated side effect function and its arguments available for a call from inside the respective shift operators for the manipulator type. We show other examples of such manipulator types later in this chapter; for instance, a manipulator type called smanip mentioned by the C++ standard. It is an implementation-defined function type returned by the standard manipulators. See the Apache C++ Standard Library Reference Guide for details.

Returning now to the example above, the manipulator object provided as an argument to the overloaded shift operator is obtained by Manip(x), which has three possible solutions:

  1. Manip(x) is a function call. In this case, Manip would be the name of a function that takes an argument of type x and returns a manipulator object of type manipT; that is, Manip is a function with the following signature:

  2. Manip(x) is a constructor call. In this case, Manip would be the name of a class with a constructor that takes an argument of type X and constructs a manipulator object of type Manip; that is, Manip and manipT would be identical:

  3. Manip(x) is a call to a function object. In this case, Manip would be an object of a class M, which defines a function call operator that takes an argument of type x and returns a manipulator object of type manipT:

Solutions 1.) and 2.) are semantically different from solution 3.). In solution 1.), Manip is a function and therefore need not be created by the user. In solution 2.), Manip is a class name and an unnamed temporary object serves as manipulator object. In solution 3.), however, the manipulator object Manip must be explicitly created by the user. Hence the user has to write:

which is somewhat inconvenient because it forces the user to know an additional name, the manipulator type manipT, and to create the manipulator object Manip. An alternative could be to provide Manip as a static or a global object at the user's convenience, but this approach would introduce the well-known order-of-initialization problems for global and static objects. For these reasons, solution 3.) is useful if the manipulator has state; that is, if it stores additional data like a manipulator, let's call it lineno, which provides the next line number each time it is inserted.

For any of the three solutions just discussed, there is also a choice of associated functions. The associated function fmanipT can be either:

a.

A static or a namespace-scope function;

b.

A static member function;

c.

A virtual member function.

Among these choices, b.), the use of a static member function, is the preferable in an object-oriented program because it permits encapsulation of the manipulator together with its associated function. This is particularly recommended if the manipulator has state, as in solution 3.), where the manipulator is a function object, and the associated function has to access the manipulator's state. Using c.), a virtual member function, introduces the overhead of a virtual function call each time the manipulator is inserted or extracted. It is useful if the manipulator has state, and the state needs to be modified by the associated manipulator function. A static member function would only be able to access the manipulator's static data; a non-static member function, however, can access the object-specific data.

33.3.3 Examples of Manipulators with Parameters

In this section, let's look at some examples of manipulators with parameters. The examples here are arbitrary combinations of solutions 1.) to 3.) for the manipulator type, with a.) to c.) for the associated function. We also use the standard manipulator std::setprecision() to demonstrate the various techniques.

Example 1: Function Pointer and Non-member Function. This example combines 1.) and c.), and so:

This implementation of the standard iostreams uses this technique for implementing most standard manipulators with parameters. See Section 28.3.2 for reference.

Example 2: Unnamed Object and Static Member Function. This example combines 2.) and b.), and thus:

The manipulator type manipT can be derived from the implementation-specific manipulator type smanip defined by iostreams. Here is an alternative implementation of a manipulator like setprecision():

Example 3: Unnamed Object and Virtual Member Function. This example 2.) and c.), and therefore:

The idea here is that the associated function fmanipT is a non-static member function of the manipulator type manipT. In such a model, the manipulator does not store a pointer to the associated function fmanipT, but defines the associated function as a pure virtual member function. Consequently, the manipulator type manipT is an abstract class, and concrete manipulator types are derived from this abstract manipulator type. They are required to implement the virtual member function that represents the associated function.

Clearly, we need a new manipulator type because the implementation-specific manipulator type smanip is implementation-defined. In thisimplementation of the C++ Standard Library, smanip has no virtual member functions, but stores a pointer to the associated function. Here is the abstract manipulator type we need:

This type virtsmanip differs from the implementation-specific manipulator type smanip in several ways:

The manipulator smanip expects a pointer to a function that takes a std::ios_base reference. In this way, a manipulator is always applicable to input and output streams, regardless of whether or not this is intended. With our new manipulator type virtsmanip, we can define manipulators that cannot inadvertently be applied to input streams.

Since we have a new manipulator type, we also need a new overloaded version of the manipulator inserter:

After these preparations, we can now provide yet another alternative implementation of a manipulator like setprecision(). This time setprecision() is a manipulator for output streams only:

Example 4: Function Object and Static Member Function. The next example combines 3.) and b.), so here:

This solution, using a function object as a manipulator, is semantically different from the previous solution in that the manipulator object has state, that is, it can store data between subsequent uses.

Let us demonstrate this technique in terms of another example: an output manipulator that inserts a certain string that is maintained by the manipulator object. Such a manipulator could be used, for instance, to insert a prefix to each line:

We would like to derive the Tag manipulator here from the implementation-specific manipulator smanip. Unfortunately, smanip is restricted to associated functions that take a std::ios_base reference as a parameter. In our example, we want to insert the stored text to the stream, so we need the stream's inserter. However, std::ios_base does not have inserters or extractors. Consequently we need a new manipulator base type, similar to smanip, that allows associated functions that take a reference to an output stream:

Then we need to define the inserter for the new manipulator type osmanip:

Now we define the function object type M, here called Tag:

Note that the semantics of this type of manipulator differ from the previous ones, and from the standard manipulator std::setprecision(). The manipulator object has to be explicitly created before it can be used, as shown in the example below:

This kind of manipulator is more flexible. In the example above, you can see that the default text is set to "v1.2 >>" when the manipulator is created. Thereafter you can use the manipulator as a parameterless manipulator and it will remember this text. You can also use it as a manipulator taking an argument, and provide it with a different argument each time you insert it.

Example 5: Function Object and Virtual Member Function. In the previous example, a static member function is used as the associated function. This has the slight disadvantage that the associated function cannot modify the manipulator's state. Should modification be necessary, you might consider using a virtual member function instead.

Our final example here is a manipulator that stores additional data, the previously mentioned lineno manipulator. It adds the next line number each time it is inserted:

The manipulator is implemented following the 3.) and b.) pattern, that is:

The manipulator object contains a line number that is initialized when the manipulator object is constructed. Each time the lineno manipulator is inserted, the line number is incremented.

For the manipulator base type, we use a slightly modified version of the manipulator type osmanip from Example 3. The changes are necessary because the associated function in this case may not be a constant member function:

The line number manipulator could be implemented like this:

Using a virtual member function as the associated manipulator function introduces the overhead of a virtual function call each time the manipulator is inserted. If it is necessary that a manipulator update its state after each insertion, a static member function does not suffice. A non-member function that is a friend of the manipulator type might do the trick. However, in an object-oriented program, you are usually advised against non-member functions that modify private or protected data members of a class with whom they are friends.



Previous fileTop of DocumentContentsIndex pageNext file