Tech Talk

Constraining Template Parameter Types

By Kevlin Henney

All that genuinely constrains what type may be given as a parameter for a template function or class is the way it is used in the program text. There is no assumption on the part of the compiler that there is more about the given type that may be checked in advance of its use in executable code. This is at once both a strength and a weakness of C++'s template mechanism: a strength in that otherwise unrelated types with a similar set of operations may be used, e.g., int, double and complex<float> all support binary operator+; a weakness because use of a function name with a type for which that function is not defined is typically not detected until link time, often with an obscure error message. Sometimes it is obvious what operations on a type are expected. For instance, the complex<> template class expects some kind of numeric that supports standard arithmetic operations. We expect complex<long double> and complex<rational> to be legitimate, but not complex<string> or complex<window>.

complex<string> sl("hi","good");
complex<string> s2("ya”,"bye");
sl += s2;
cout << sl << endl;

Providing reasonable names for template parameters can go some way to clarifying what is intended. For a numeric type, such as required by complex<> and valarray<>, numeric or numeric_type are more helpful names than either type or T, which incorrectly suggest that a more general type is acceptable. It is possible to be yet more precise using some form of specification, as the STL has done, outlining minimum requirements for a type, e.g.., any type used with a container class must have an assignment operator and a copy constructor, amongst other things. Such documentation is external to the code, but is nonetheless useful.

On the whole most type substitution errors, and certainly all those relating to function signatures, will be picked up by link time. The techniques outlined above merely help in prevention. The errors that slip through tend to be semantic constraints that may not manifest themselves until run time. We may have very good reasons for restricting the expected type, e.g., where memory management, persistence, low level mapping, or mixed language programming are issues.

Elaborate

Until recently the class keyword has been the only way to introduce a type name in a template argument list. Out of the original C virtue of keyword conservation, class was pressed into service to indicate any type in this context and not just a user-defined type. Having a minimal set of keywords is an ideal that ended up on the cutting room floor . There is now a better candidate for the job, typename, and it is one that developers should use in preference to class when a class type is not necessarily required.

template<typename numb_type> . . .

Clearly reads more accurately than

template<class numb_type> . . .

Originally introduced for different reasons, in this context the typename keyword is somewhat self explanatory — the use of class to represent type names other than classes always requires explanation.

One hopes that history will consign this use of class to the dustbin; eventually when class is used it will mean just that. There is, however, a technique that may currently be used to con- strain a template type argument in this fashion:

template<class val_typ>
class container_of_class
{
public:
  typedef class val_typ val_typ;
  . . .
};

I have used class in the template argument list here because I actually mean it [NB: whenever I use the word class, I am also referring to struct]. By elaborating it later with the class keyword I have constrained it to not be a built-in type, a union or an enum. The same trick works with elaborating as union or enum, although in these cases typename should be used in the argument list as neither union nor enum received the same privilege as class in this context

A typedef of a user defined type name as itself is harmless and often pointless except for compatibility with C code, i.e.,

typedef struct type { ... } type;

However, in the container_of_class<> example above it has the effect of exporting the template parameter as a public member of the class. It would otherwise be visible only within the class scope.

Of course, the key to the technique is the elaboration, wherever it occurs, and the export technique is simply another useful technique which does not in itself require elaboration. You may decide to use different names, to make this typedef private, or to just use the elaboration at one or more points where the type is used within the class definition. This is needed particularly where a template function rather than a class is being constrained.

Arithmetic types

We may have something like the opposite requirement: the type parameter must be a built-in arithmetic type (int, char, double, enum, bool, unsigned long, etc.). To enforce this constraint we need some feature of the language that accepts all of these types and no others.

Looking closely we see that all of these can be assigned, using either implicit or explicit casts, the value 0. We can use a dummy variable that in some way depends on this value:

template<typename num> class some_numeric
{
  . . .
private:
  // parameter type constraint
  enum{ constraint=int(num(0)) };
};

The cast to an int is to cater for the floating point types. This class will only compile when the expression initializing the enum constant is legal. As the constant is a dummy value, and hence not intended for use, I have made it private with a hopefully meaningful name as documentation - such techniques rely on generating errors at build time, so this is quite important.

Integral types

If we are interested only in integral types (arithmetic types excluding the floating point types), we could simply remove the cast to int used in the previous example. Alternatively we can use another context where only integral types are permitted: bit fields. Here it is convenient to encapsulate the constraint in a class:

template<typename integral>
class integral_only
{
private:
  integral : 0 ;
)

An anonymous alignment field is used to enforce the constraint in an otherwise functionality-free class. We would use it as follows:

template<typename integral>
class some_class
(
  // parameter type constraint
  static const integral_only<integral> constraint;
};

To be strictly correct we also need a definition for constraint, not shown here.

The constraint variable is never accessed, so it is possible that on some existing systems it will simply be ignored and the constraint will go unchecked. If this is the case either reference it somewhere else, doing nothing with it, or move the static from class to block scope in a function guaranteed to be called, e.g., a constructor or destructor.

Some systems even allow you to get away with a completely stateless constraint. Merely mentioning the constraint type in a typedef is enough to cause attempted template instantiation. and thus validation of the constraint:

template<typename integral> class some_class
{
  // non-portable constraint
  typedef integral_only<integral> constraint;
};

However, it is unwise to rely on this as many other systems do not bother instantiating the template unless it is used to define an object.

Integers only

What if we genuinely only want integer types, i.e., the integrals excluding enums? One solution would be to take the solution just given, give the bit field a size, name it, and initialize it to 0 in a dummy constructor. Because of the stronger typing in C++ this is not a legal assignment for enums and it will only compile for genuine built-in integer types. Alternatively we can take advantage of the, relatively recent, addition to the language of static const initializers within a class definition.

These are only valid for integral types and must be initialized by compile time constants. We also avoid executing any code as a consequence of introducing a constraint - true, that a constructor initializing a bit field is not a great cause for concern, but we would rather not introduce any executable code as a consequence of our type checking efforts.

template<typename int_type> class ints_only
{
  // parameter type constraint
  static const int_type constraint = 0;
};

Again the use of a 0 excludes enums. Thus the following types are legal:

ints_only<int>
ints_only<unsigned char>
ints_only<bool>

And the following are not:

ints_only<double>
ints_only<void*>
ints_only<string>

Derived classes

A common requirement would be to constrain a class parameter to be derived from a particular class. In other words, constrained genericity similar to that found in languages like Eiffel. Some may be tempted to use assert and RTTI, but this is an abuse of both these features. The errors are of static type and, as such, are statically detectable with a little lateral thinking. The technique here is again to use dummy statics and create a general purpose constraint class

template <class base, class der>
class subclass
(
  static const base* const substitutable;
  static const size_t not_void = sizeof(base);
};

The key to the substitutability constraint is in its definition:

template<class base, class der>
const base* const
   subclass<base, der>::subable = (const der*) 0;

The explicit cast ensures that der is either base or a class derived from it, otherwise it will fail to build. The other constant is simply there to ensure that base is not void, since a pointer to any type is certainly substitutable for a pointer to void but it is illegal to take sizeof(void). Assuming sensible naming, the following are legal:

subclass<window, graph_display>
subclass<socket_address, URL>
subclass<ostream. ofstream>

And the following are not

subclass<void, any>
subclass<long, int>
Subclass<window, text>

Summary

The key to all the techniques described here is in forcing a failed build: static error detection is far superior to dynamic error detection. Where this involves operational compatibility it is clearly quite simple, and is the basis of C++’s template system. For our other constraints we can use, quite literally, a more declarative approach and in such a way that also acts as a form of documentation.

Enforcing compile time constraints using templates is a theme I will be returning to in future articles.

Kevlin Henney
kevlin@curbralan.com

[This article was first published in ACCU’s February 1996 Overload magazine, Issue 12. Because this area of C++ has not changed since this article was originally published, I thought its lessons and value warranted it being reprinted here—Ed.]

Editorial

By Reg. Charney

Linux Journal Welcome

We are delighted to welcome Linux Journal as one of our sponsors. They are the premier magazine covering the Open Source movement, and Linux in particular. They are an excellent source for all things related to Linux, particularly commercial vendors and products.

Dilbert-esque

A major Valley company has found a unique way of energizing its people. It is posting “Don’t waste your time” signs everywhere. From a marketing perspective, this message is a disaster: it suggested no remedy, no solution, no product nor a course of action. The sign makers are implying its employees are time-wasters or worse. It was tempting to scrawl on one or more signs, addendums like: “… by reading this message” or “… by writing on this sign.” or “… - Go fishing”. I am sure you can come up with better responses than mine. The best one that I receive at signs@accu-usa.org will get $20. The runner-up will get $10. They will be printed in next month’s issue. Think of all the encouragement you will give.

Quote of the Week

I just met with an intelligent and thoughtful Microsoft software engineer. We spoke about the Open Source movement and Apache came up. His comment to me was “Microsoft IIS knocked the socks off of Apache until IBM put 10 full time engineers on it. Now Apache is 1000 times better.” I am not trying to start a flame war here since I believe he was sincere in his beliefs and statement, but where does that place IIS relative to Apache now? Given that Apache was not create by “full-time” people, how did it become such a staple in the market place and why would IBM commit to it instead of to a home-grown solution? Comments?

FTP on our site

You can now download files, newsletters, and programs from our site: www.accu-usa.org. Enjoy!

Cool Classes

By Reg. Charney

Destructor Power

Good housekeeping is a sign of a good program. Unfortunately, in the rush to produce results in an ever more complex application, cleaning up after yourself gets more and more difficult and more and more essential. This is where the simple concept of constructors and destructors can be a life saver. In the Windows systems, there are many error codes with corresponding error messages. There is a complex function, FormatMessage(…), that returns the text that corresponds to a given error code. In part, it does this by passing back a pointer to an internal buffer containing the text of the message. You must remember to free this buffer after you have done with it. If you are like me, you sometimes forget. This little class, EM, uses its constructor and destructor to do all that while simplifying the error message interface. It also defines some intuitive conversion operators to extract the needed code and text. I expect that there may be some comments on this latter technique, but it does reduce namespace pollution.

If there is no corresponding error message text for a given error code, the text pointer is set to NULL. This can be tested and acted on accordingly.

The first part of the test program loops through the first 10 possible error codes and supplies them to the EM instance. The last part of the test program tries to create an invalid directory and lets the system set the error code which is then captured and reported on.

This class is not thread-safe because FormatMessage is not thread safe. Any ideas on how to make this thread-safe?

// EM - Error Message class
// Used to avoid memory leaks

#include "stdafx.h"
#include <windows.h>
const DWORD MAX_LEN = 256;

class EM
{
  LPTSTR lpBuf; // from call
  DWORD dwErr; // current err

private:
  // Helper function
  void Init(void)
  {
    int len; // message len
    len = FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dwErr,
            MAKELANGID(LANG_NEUTRAL,
            SUBLANG_DEFAULT),
            (LPTSTR)&lpBuf,
            MAX_LEN,
            NULL);
    if (! len)
      lpBuf = NULL;
  }

public:
  inline EM()
  {
    dwErr = GetLastError();
    Init();
  }

  inline EM(const DWORD dwE) : dwErr(dwE) { Init(); }
  inline ~EM() { LocalFree(lpBuf); }
  inline operator LPCTSTR() const { return LPCTSTR(lpBuf); }
  inline operator LPTSTR() const { return lpBuf; }
  inline operator DWORD() const { return dwErr; }
};

int main(int argc, char* argv[])
{
  // print error messages
  // for error codes 0-9

  for (DWORD i = 0; i < 10; ++i)
  {
    EM em(i);
    if (LPCTSTR(em))
      printf("%5d %s", DWORD(i), LPTSTR(em));
  }

  // show default behaviour

  printf("\nGenerate an error by creating an invalid directory.\n\n");

  CreateDirectory("*BadName*", 0);

  EM em; // capture system error

  printf("Code=%d Text=%s\n", DWORD(em), LPTSTR(em));

  return 0;

}

Trends

By Reg. Charney

Languages

Over the last fifteen months, the major growth in languages has been those directly related to the Internet. Thus, XML, Perl, HTML, and Java have flourished as shown in Chart #1. However, even these high-flyers have suffered reverses in the last few months as shown in Chart #2. It is interesting to note that the highest flyers are also showing the most deceleration in demand.

Platforms

In this issue, you may have noticed some platform omissions including Oracle 2000 and AIX. While there are many users on these platforms, their growth on a month by month basis and over the last fifteen months has been so minimal that they will no longer be included. I will let you interpret that as you see fit.

One of the major changes in the demand deceleration of platforms is that Windows 2000 has now joined the other platforms in a decreased demand. If you recall, last month, Windows 2000 was the only platform to buck the downward trend. Interestingly enough, while individual flavors of Windows have decreased in demand, overall demand for Windows is still growing, albeit slowly. This includes all dialects of Windows.

What’s is Hot, What is Not

You must have been on an island not to know that LinuxWorld was in San Jose in August. It had so many exhibitors that some of them overflowed the exhibits hall and ended up out in the hall ways. Rumor has it that the next year’s West Coast LinuxWorld will move to San Francisco’s Moscone Centre to handle the larger crowds expected next year. There was also a lot of big names there: IBM, Dell, Compaq SGI, HP, Sun, as well as the usual Linux crowd like our own VA Linux and all the major Linux distributors. The BSD folks also had an interesting presence.

If anything could be said about future trends, it had to be “Linux for embedded systems”. In contrast, last year’s theme was Linux in the business world. This year delivered on a lot of that promise. Most of the software exhibitors were providers of B2B solutions. Two products caught my eye.

PocketLinux’s Linux on a Compaq iPaq is a color PDA running Linux and can be Internet enabled. It will give Palm a real run for its money. Cost about $500. There is also a B&W Helio for about $200 (www.PocketLinux.com).

eGrail (www.eGrail.org) is an Open Source provider of content management software for the web. It is server based with access to the central repository and tools through any browser. It is a really slick solution.