|
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.]
By Reg. Charney
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.
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.
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?
You can now download files, newsletters, and programs from our
site: www.accu-usa.org.
Enjoy!
By Reg. Charney
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;
}
|
By Reg. Charney
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.
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.

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.
|