Channels ▼
RSS

Using Template Functions to Customize Library Behavior


March 2001/Using Template Functions to Customize Library Behavior

Using Template Functions to Customize Library Behavior

Michael B. Yoder

If you’re writing code to be used by other programmers, you’ll want to make it customizable, and you’ll want to do it well. This article presents an approach that is both efficient and easy to manage.


Introduction

One of the many challenges of writing a C++ library is anticipating the many different ways clients will use your code. Despite your best prophetic efforts, however, the real world always exposes the unforeseen. Unfortunately, by the time your library is in the hands of users, implementing enhancements can become difficult. Conformance to a specification, a frozen API, or backward compatibility can be serious obstacles to how you go about adding functionality.

Even if your library isn’t technically bound, it can become effectively so restrained by widespread usage in client code. One faction of users who need an enhancement might be perfectly willing to undergo the cost of source code changes and/or recompilation, but others might not be so enthusiastic.

As such, library developers are always looking for ways to design for future customization. When looking to leave algorithms or policies open, developers may turn to the Strategy Pattern [1] or the now canonical approach used in the C++ Standard library known as traits [2]. Depending on what modifications you are in a position to make, it could be more difficult to take advantage of these once your classes are in use. A cutting-edge and less invasive way of applying traits to hierarchies of classes is discussed in Andrei Alexandrescu’s recent article "Traits on Steroids." [3]

This article looks at function templates and how they can be used to provide hooks for customizing behavior.

Looking for a Solution

Consider the following working example. Assume you want to customize a policy in your classes, perhaps to specify the handling of a particular resource. Resources may include such things as file handles, network connections, mutexes, or memory. Of course, you want a solution that will give users maximum flexibility yet keep the common case trivial.

Adding new template or function parameters after the fact is not really a solution. It provides flexibility, but could break backward compatibility or require user source code changes. If users are vested in the status quo, as described above, or if they differ in basic priorities, such as space vs. time efficiency, you need to look at other solutions. I will examine three.

Using the Preprocessor

A quick and dirty method to accommodate policy change is to use macros. In Listing 1, you will notice that the macro is placed in what would be a private library header file. It is braced with an #ifdef so that the default value may be overridden. The container class is really more complex than needed here, but I introduce it this way so I can refer to it throughout this article in other examples. The container class uses the macro in the member function doPolicy, which is called whenever the policy needs to be executed.

In this contrived example, the user code requests a service from the class, which calls the doPolicy method to complete the demonstration. The output of this program is:

Policy is: default policy

The programmer can then use the compiler’s facility for predefining macros to change the value of CLASS_POLICY to something else. For example, on my compiler, if you use the flag -DCLASS_POLICY='my policy' at the command line when compiling both the library and user code, the output dutifully reports:

Policy is: my policy

As you no doubt have realized by now, the quick and dirty way is not the best way. While there might be an occasional user who doesn’t mind this method, overall it doesn’t critique well. For one thing, the policy must be set in both the library and the client code by way of recompilation. For another, the method is not practically extensible. What if you wanted to set different policies for different classes or groups of classes, or to exercise control at run time? Creating a list of macros would be awkward at best.

But this route has an even darker side-aspect. Not only would users have to recompile both the library and client code to change the policy, they would need to ensure that all their defines were 100% consistent. Failure to do this would lead to different definitions of the function in various translation units — a violation of the One Definition Rule [4]. This could easily result in unpredictable behavior. Worse yet, these errors would likely be silent, their presence going undetected by the compiler and linker.

And of course, run-time control with a preprocessor mechanism is impossible. In short, using macros is unpleasant for everyone involved.

Using a Run-time Service

Another tack is to have the library provide a service for registering new policies. All the subscribing classes would forward their policy requests to a Singleton [5, 6] provided by the library, which would in turn use either a default policy or a registered policy. Listing 2 shows an implementation of this approach.

The singleton would know each class by an identifier which would accompany its policy request. Users could override the default behavior by providing a callback function to which policy requests would be forwarded, and registering it with the policy manager. Of course, in the real world, these global callback functions would be better off in a namespace, and the static map structure used for the registry would need to be guarded for multithreaded access. The output of the user program for this example is:

Performing Default Policy.

Performing My Policy.

This method offers at least one advantage over macros. Before registering, the default is executed; afterward, the call is forwarded to the function supplied by the user. In this setup, users gain at least some degree of control. Behavior can be specified at run time with no need to recompile the library.

Things are still not as nice as we would like, however. Although these customized policies are known at compile time, the program still must pay for a run-time lookup of their policy function. In addition, the hard-coded string that each class passes in is a poor subset of the unbounded set of types that it would be possible to choose from, and is in fact limited to one customization per class template.

Using Function Templates

Function templates provide the degree of control required. Compile-time decisions can be enforced, while leaving run-time customization as an option. With templates, it is possible to set up compile-time customization to handle both distinct types and related groups of types as well. Function templates do behave a bit differently than class templates in C++, however. If you are interested in learning more about how they work, see the sidebar "Understanding Function Template Selection."

Listing 3 shows the use of function templates in action. In the library, the policy function becomes a function template instead of a member function. The MyContainer member function doPolicy is modified from the previous example to call policy with its this pointer as an argument. The use of the this pointer ensures that the policy function will always be called with the container’s specialized type as an argument.

In the user.cpp file, the user provides an overload for policy which is a more specialized function template than the primary template defined in library.h. The compiler will use this new template if policy is called with any argument that is a specialization of MyContainer on Element2<T> types. Listing 3 once again gives familiar results, but without the run-time lookup cost:

Performing Default Policy

Performing My Policy

Using function templates in this way allows you to transcend the other solutions. Now you can customize on a specific type or type grouping at user compile time. Run-time control can be reserved for fine-grained behaviors, down to instances of classes. In Listing 4, I bring back the registration service, which now allows users to implement control over class instances at run time. This function implements behavior for different instances using the same scheme as in Listing 2. In this version, however, a run-time lookup cost need be paid only if a policy is based on run-time information. Listing 4 demonstrates both compile-time and run-time policies used together.

The user program provides overloads of policy that are more specialized. The first overload is more specialized on arguments of any MyContainer<T> type, and it provides a different compile-time policy. The second overload is more specialized than the first overload; the compiler will select it when the argument is a MyContainer specialized on any Element2<T> type. This overload provides an example of run-time policies. The output of Listing 4 shows different policies being executed according to the different types and instances being used:

Performing Compile Time Policy for types of:
MyContainer<Element1<T>
    on Object: MyContainer<Element1<int> >

Performing Runtime Policy:
    Object: MyContainer<Element2<float> > Instance#1
    runTimePolicy1() executed
Performing Runtime Policy:
    Object: MyContainer<Element2<float> > Instance#2
    runTimePolicy2() executed
Performing Runtime Policy:
    Object: MyContainer<Element2<float> > Instance#3
    runTimePolicy3() executed

A Caveat

There is one limiting factor to consider when using function templates as described: how this technique interacts with explicit and implicit template instantiations within your library. Explicit template instantiations are sometimes intentionally placed in a library to help reduce the size of user executables. If your library takes advantage of some of its own template classes, they will be implicitly instantiated as well. If any of these classes use a template function as described, the compiler will, of course, generate the needed function. Since all this is now compiled in the library, a user’s function template will not be considered.

A workaround to prevent implicit instantiations from foiling this scheme is to use internal types in your library for its own classes. For example, if your library needs to use MyContainer<Element1<int> >, you can create a tight wrapper for int and use MyContainer<Element1<lib_int> > instead.

If the library needs to use explicit instantiations to help reduce the size of user executables, a workaround is more difficult. If the class method that uses the policy is made virtual, users needing to customize behavior could create subclasses and provide an override. The compiler would then consider anew its function template choices during compilation of the subclass type. Of course, users doing this would pay for customizing behavior by having the instantiations increase the size of their executables once again.

Function Templates and ANSI Compliance

The examples used in this article were compiled successfully, with only minor tweaking, using these compilers: g++ 2.95.2 on Red Hat Linux, Sunpro 5.1 on Solaris, aCC 3.13 on HP-UX, Visual Age 5.0 on AIX, MipsPro 7.3.1.1m on SGI Irix, and Compaq C++ 6.2-024 on Tru64. If your compiler doesn’t offer complete support for partial ordering of function templates, a likely symptom would be compiler ambiguity errors. A workaround for this problem is to provide alternate function templates that are completely specialized. For example, instead of this function template from Listing 3:

template<class T>
void policy(const MyContainer<Element2<T> > * c);

Use a completely specific version like this:

template<>
void policy(const MyContainer<Element2<float> > * c);

While this technique doesn’t allow for customizing groups of types, it does preserve the ability to customize at user compile time.

Acknowledgments

I would like to thank my friend and colleague Martin Sebor without whom this article would not have been possible. Also, thanks to Vijay Rajagopalan, the Foundation Team, and Technical Publications at Rogue Wave for their help and support.

References

[1] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns, Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995), p. 315.

[2] Nathan C. Myers. "Traits: a New and Useful Template Technique," C++ Report, June 1995.

[3] Andrei Alexandrexcu. "Traits on Steriods," C++ Report, June 2000.

[4] Bjarne Stroustrup. The C++ Programming Langauge, Third Edition (Addison-Wesley, 1997), p. 203.

[5] Gamma, et al. Design Patterns, p. 127.

[6] Scott Meyers. Effective C++, Second Edition (Addison-Wesley, 1998), p. 222.

Michael B. Yoder is a software engineer at Rogue Wave Software. For the past two and a half years he has worked as a part of the Foundation Group in teams responsible for Rogue Wave’s Tools.h++ Library and C++ Standard Library implementation. He has a B.S. in Computer Science from Oregon State University.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video