Columns


Standard C/C++

Implementing <complex>

P.J. Plauger


P.J. Plauger is senior editor of C/C++ Users Journal. He is convener of the ISO C standards committee, WG14, and active on the C++ committee, WG21. His latest books are The Draft Standard C++ Library, and Programming on Purpose (three volumes), all published by Prentice-Hall. You can reach him at pjp@plauger.com.

Introduction

Last month, I introduced the header <complex> as it is described by the draft C++ Standard. (See "Standard C/C++: The Header <complex>," CUJ, October 1995.) It offers fairly ambitious support for complex arithmetic in all three floating-point precisions, including a wide assortment of complex math functions and interconversions among the three complex types.

I continue that discussion with a description of one way to implement the header <complex>. Listing 1, in fact, shows the entire implementation from my version of the Standard C++ library (aside from supporting routines in the Standard C library). Because the file is so large, I provide here only a brief guided tour to supplement it. The header <complex> raises some interesting implementation issues, so it warrants a bit of study.

Picking the Supported Types

The draft C++ Standard bases all complex arithmetic on a template class complex<T>. Here T is the type of the real and imaginary components stored in each object of class complex<T>. This formulation strongly suggests that the draft mandates complex arithmetic based on any type T. Doubtless, that is an overstatement.

For a type to be useful, it must satisfy some reasonable arithmetic properties. (Imagine a complex number based on istream, if you will.) The library certainly has to be able to add, subtract, multiply, and divide the components of an imaginary number — with the expected behavior of each of these operators. We can guess that the draft meant to require at least this much common-sense behavior. (And that rules out the integer types, which don't behave at all well under division.)

But the library might even be called upon to compute an occasional cosine of exponential as well. And herein lies a fundamental question. Can it assume that the user will supply all necessary functions, or must it be prepared to compute them for an arbitrary type? The former makes for a less gracious user interface, while the latter can impose serious demands on an implementation.

Sadly, the draft fails to mention any constraints at all. A conservative reading of the draft can easily support the notion that only the three floating-point specializations are required. Sure, there's a template class complex, but nobody is supposed to really use it. Just use the specializations and forget about the template in practice.

One use for templates, in fact, is merely to provide uniform naming rules, without mandating any uniformity among the specializations that share common names. Perhaps the best example of this approach is the recently introduced template class numeric_limits. The specialization numeric_limits<unsigned short>, for example, tells you rather more than the traditional C header <limits.h> does about unsigned short.

The analogy isn't perfect. For one thing, the template class numeric_limits can be instantiated for any type. It just happens to be remarkably uninformative about any but the built-in types for which it is specialized. By contrast, the typical user of template class complex would expect numeric results that are at least in the right ball park.

An aggressive reading of the draft yields quite a different answer. You can argue that any component type that has floating-point style arithmetic should be tolerated. An arbitrary-precision rational number fits this requirement. How, then, does a reasonable implementation compute the complex hyperbolic cosine in an unknown representation, using just add, subtract, multiply, and divide? The mind boggles.

A Middle Ground

I decided to aim for a middle ground, at least until such time as the draft becomes clearer. My design goal was to accept as the component type T any type that can be converted to and from type double. If it is one of the three floating-point types, all arithmetic takes place at the precision of the component type. Otherwise, all arithmetic occurs in double precision, converting to and from the component type as needed. If you'd like more precision, you're out of luck.

Well, you're mostly out of luck. It is an easy matter to change the logic to work in long double, if maximum precision is more important than maximum performance. But even that may not be enough precision for some applications. So I thought it desirable to provide some way, however nonstandard, for sophisticated programmers to make use of the header <complex> with even the most exotic of classes.

The result was a "traits" class, the template class_Ctr<T>. (For those new to this series, names beginning with an underscore and an uppercase letter are reserved to the implementor, so there is no fear of colliding with user-defined macros.) Traits classes are used to advantage in defining the template class basic_string, for example. (See "Standard C/C++: Implementing <string>," CUJ, August 1995.) They capture in one place an assortment of information associated with a particular template type parameter — types, values, functions, even static storage.

Listing 1 begins with the definition of template class_Ctr<T>. It contains a slew of static member functions. Each of these functions supplies a useful value (such as the representation of infinity) or performs a useful calculation (such as the cosine of a value) for the specified type T. As I indicated above, the template definition assumes only that you can convert back and forth between types double and T. Other functions in this header make use of the basic four arithmetic operations, described above. Otherwise, nothing else need be known about the properties of class T.

Immediately following the template class definition are three specializations, for the floating-point types float, double, and long double. These specializations ensure that the most appropriate arithmetic is performed in each case. An application that needs an exotic brand of complex arithmetic would do well to supply a similarly tailored specialization of _Ctr<T> for the peculiar class T in question.

Template Class complex

Defining class complex itself involves the same kind of work as for the traits class. The implementation must supply the template class proper and three specializations. In this case, however, the code is far more repetitious. To avoid wholesale duplication of code, I made use of yet another trick that is becoming widespread in the draft Standard C++ library — I introduced a base class that distills out the common code.

In this case, the base class is a template class, so it's not quite as common as it appears. The template class _Complex_base<T> captures the common arithmetic operators and component access functions. It is, of course, instantiated differently for each of the floating-point types, not to mention any other types for which the program instantiates template class complex<T>.

Template class _Complex_base<T> also hides, or at least mostly hides, another problem. Like so many other parts of the draft Standard C++ library, this header makes extensive use of member template functions. Unfortunately, few if any commercially available compilers currently support this fairly recent addition to the C++ language. As a practical matter, it is necessary to provide an alternative form wherever a member template function occurs.

I use the macro _HAS_MEMBER_TEMPLATES throughout my library to conditionally include alternate forms of code. The standard-conforming version (which I have yet to exercise) assumes that member template functions are implemented. An alternative is designed to compile with widely available technology.

In this particular case, most of the bulk of template class _Complex_base<T> goes away if member template functions are not supported. In their place are added versions of real and imag which permit the components to be altered individually from outside the class. This breaks the encapsulation designed into template class complex, but it seems to be a necessary evil in the interim.

The alternate form of these member template functions occurs further down the file, after the declarations for the variations of template class complex. The behavior of these external template functions is not entirely identical to the creatures they replace, but it's quite close enough for everyday uses.

Supporting Functions

The remainder of Listing 1 shows the various template functions defined for arguments of class complex<T>. A lot of technology goes into these functions, more than I can explain here. I described them in more detail in a two-part series of columns I wrote for Embedded Systems Programming, a sister publication to CUJ. (See "State of the Art: Complex Made Simple," ESP, July 1994, and "State of the Art: Complex Math Functions," ESP, August 1994.)

The constants used in these functions assume a maximum precision of 64 bits (about 20 decimal digits). That works fine for all current computers, except for the Sun Sparc. It can supply 112 bits of precision for type long double. The code is easily repaired by extending the precision of the constants.

This article is excerpted in part from P.J. Plauger, The Draft Standard C++ Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995).