| Tech Talk Constraining Template Parameter Types Editorial Linux Journal Welcome Dilbert-esque Quote of the Week FTP on our site Cool Classes Destructor Power Trends Languages Platforms What's Hot, What's Not | 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 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 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. |