|
By Bill Gibbons, Pixo, Inc.
Copyright 2000 © Bill Gibbons
All rights reserved.
The Problem
Sometimes it is useful to declare a variable with a type that is
not known directly, but is known to be the type of this
expression. For example:
void
f(LibClass *p)
{
typeof(p->val) value=p->val;
/* ... */
} |
where typeof(p->val)
yields a type that is used to declare the local val.
This is particularly useful in templates, where many of the types in
use are not known ahead of time.
Some compilers implement a typeof
keyword as an extension, but code that uses such an extension is not
portable. This article describes a portable way to write a typeof
operator.
Almost Enough
C++ has almost enough functionality to do typeof.
Function templates can be used to extract a type from an expression
and declare a typedef of that type:
template<class
T> void f(T) { typedef T TheType; }
void g() { f(123); } // TheType is int |
Here the typedef TheType
in the instantiated function template f<int>
has the same type as the expression 123.
But, there is no way to export the type from the function, so by
itself this is not useful for implementing typeof.
Similarly, overloading can be used to select a function that
contains a typedef that matches the type:
void
f(char) { typedef int TheType; }
void f(short) ( typedef short TheType; }
void g() { f('x'); } // TheType is "char" |
but there is no way to extract the type.
Class templates can be used to map a value or type to another
value or type:
template<class
T> struct U; // no definition needed
template<> struct U<char> { typedef unsigned char
type; };
template<> struct U<short>{ typedef unsigned short
type; };
void f()
{
U<char>::type a; //unsigned
char
U<short>::type b; //unsigned short
} |
But you cannot use a class template to extract a type from an
expression, as you can with function templates or overloading. (If
the expression is a name with external linkage it is possible to
implement typeof
with class templates, but this is not very useful.)
Function templates and overloaded functions can also map an
expression of one type into an expression of another type, as in:
unsigned
char U(char x) { return x; }
unsigned short U(short x) { return x; }
void f()
{
short s = 0;
s; //
expression "s" has type "short"
U(s); // expression
"U(s)" has type "unsigned short"
} |
This is also not sufficient, although it turns out to be part of
the solution.
We need some way to map the type of an expression to something
that can be used as a template argument. The only construct in the
language that can do this is sizeof.
It happens that sizeof
is sufficient.
The Solution
The trick is to map each type to a unique integer value using
overloaded functions and sizeof.
Then template specializations can map the integer value to the right
type:
- The expression is passed to an overloaded function.
- The return type of the particular function selected is pointer
to array of char,
with the array size being unique to the type.
- The sizeof
operator extracts the array size as a constant. The function is
never actually called and so does not need a definition.
- The constant is used to select a class template among a set of
specializations; the selected class template contains a typedef
with the right type.
Simple Example
The following example shows a simple typeof
that handles expressions of type short,
int
and long.
template<int
N> struct typeof_c;// No def'n, only specializations
template<> struct typeof_c<1> { typedef short V;
};
template<> struct typeof_c<2> { typedef int V; };
template<> struct typeof_c<3> { typedef long V; };
typedef
char CharArrayOf1[1];
typedef char CharArrayOf2[2];
typedef char CharArrayOf3[3];
typedef
CharArrayOf1 *PtrCharArrayOf1;
typedef CharArrayOf2 *PtrCharArrayOf2;
typedef CharArrayOf3 *PtrCharArrayOf3;
PtrCharArrayOf1
typeof_f(short); // No definitions needed
PtrCharArrayOf2 typeof_f(int);
PtrCharArrayOf3 typeof_f(long);
#define
typeof(x) typeof_c<sizeof(*typeof_f(x))>::V |
Consider what happens to
typeof(123L):
- The macro expands to typeof_c<sizeof(*typeof_f(123L))>::V.
- The call typeof_f(123L)
selects the following declaration from the set of overloaded
function declarations:
PtrCharArrayOf3
typeof_f(long);
- The type of typeof_f(123L)
is PtrCharArrayOf3,
or pointer to array of char length 3.
- The type of *typeof_f(123L)
is an array of char length 3.
- sizeof(*typeof_f(123L))
is an integral constant expression with the value 3. Since the
operand of sizeof
is not evaluated there is no need to have a definition for typeof_f.
- The template-id typeof_c<sizeof(*typeof_f(123L))>
is effectively typeof_c<3>
which selects the specialization:
template<> struct typeof_c<3>
{ typedef long V; };
- The qualified-id typeof_c<
sizeof(*typeof_f(123L)) >::V is the above typedef which has type
long.
- So typeof(123L)
refers to a typedef of type long.
Note that all of these operations happen at compile time. The
generated code will be the same as if the type were written as long
instead of typeof(123L).
Improvements
The typedefs
in the preceding example are not really necessary and they pollute
the global namespace. We can eliminate them at the risk of confusing
human readers (and maybe a few compilers):
char(*typeof_f(short))[1];
// fct returns ptr to array of char len 1
char(*typeof_f(int ))[2]; // length 2
char(*typeof_f(long ))[3]; // length 3 |
If we declare the parameters as references to const instead of
using simple pass by value, it becomes possible to use expressions
which cannot be passed by value for example, objects of classes
with private copy constructors. So in the general case, given type T,
we want to write the typeof_f
declaration for T
as:
|
char(*typeof_f(const
T&))[nnn];
|
Declaring the required specializations and functions can be
handled much more easily with a macro:
#define
REGISTER_TYPEOF(N,T) \
template<> struct typeof_c<N> { typedef T
V; }; \
char(*typeof_f(const T&))[N]; |
However, we run into trouble with some compound types. For
example, if we expand:
|
REGISTER_TYPEOF(
3, void (*)() )
|
we get:
template<>
struct typeof_c<3> { typedef void (*)() V; };
char(*typeof_f(const void(*)()&))[3];
|
Both of these declarations are ill-formed because combining C++
types is not as simple as substituting a type for a type name. We
can make the macro work again by introducing another class template:
| template<class
T> struct WrapType { typedef T U; }; |
This template takes advantage of the fact that when you
instantiate a template with a compound type, e.g.
you create a simple name (the template parameter, in this case, T)
for the type. We cant get to T
directly from outside the template but we can get to typedef U
that has the same type. In example:
| WrapType<void
(*)()> :: U |
has type void (*)().
So if we replace instances of T
in the macro with WrapType<T>::U
we have the original type but in a form that we can use to build
other types.
Our macro now looks like this:
#define
REGISTER_TYPEOF(N,T) \
template<> struct typeof_c<N> {
typedef WrapType<T>::U V; }; \
char (*typeof_f(const WrapType<T>::U
&))[N]; |
See the program listing at the end for a more complete example
using the above macro and several test types.
Limitations
This approach does require that each type that might be used in typeof
be explicitly registered with the REGISTER_TYPEOF
macro. This limits, or at least places additional burden, on the use
of this technique in general-purpose template libraries.
#define
REGISTER_TYPEOF(T) \
template<> struct typeof_c<__LINE__>
\
{ typedef WrapType<T>::U V; }; \
char (*typeof_f(const WrapType<T>::U
&))[__LINE__]; |
The need to assign a distinct ordinal to each type could be a
nuisance. If all the REGISTER_TYPEOF
uses are in a single include file the macro could be simplified by
using __LINE__
instead of N:
It does not matter that the ordinals may be large and/or
discontiguous; the only restriction is that they be positive and
distinct.
If the type of the expression given to typeof
has not been registered, the resulting error message may be cryptic;
but it should at least refer to typeof_f
that should give a clue to the problem.
Conclusion
For applications that need a typeof
operator this technique provides most of the power of a compiler
extension while keeping the code portable. This general approach of
using function overloading and sizeof
to extract expression type information can be used in other places.
The implementation of this technique can be somewhat obscure but
the uses (such as reference to typeof)
can be kept simple. Since all of these operations occur at compile
time there is no runtime cost for using them.
A complete source
code example can be found on page 4 of the printed newsletter.
By Reg. Charney
As you have probably noticed, things have changed again!. Based
on the possibility of new sponsor, we have expanded things to
increase this newsletter by 50% and to add color, at least to the
first and last page. We have also been able to expand the length and
content of our technical offerings.
We are also planning to include several new sections. They
include a book review section, and a technical summary section. The
technical summary will try to summarize technical topics on a number
of the C, C++ and Java reflectors that exist on the net. Let us know
what more you would like.
As this issue is about to go to press, one of our new potential
sponsors has backed out or more accurately, failed to pay the
invoice, answer emails, or respond to phone messages. It is a
disappointment, as you can imagine. It is also too late to change
the content or layout of this issue. However, with your assistance,
we can grow stronger from the experience.
If you know firms that are committed to open source development
or in the betterment of the professional programming community and
the people they hire, please let them know about the ACCU and in
this newsletter. Also, please tell me and I will contact them. We
need sponsors and also advertisers. The benefits package that we are
offering sponsors is quite impressive.
We can now send you future issues of this newsletter as PDF
files. To subscribe, please email me at
accent-subscribe_AT_CharneyDay.com
Past issues are also available as PDF files, including versions
in A4 format for our international readers. In the body of the
message say if you want the A4 format and/or past issues.
By Reg. Charney
Again, Java leads the pack in normalized demand. However, this
month HTML, Perl and XML all are in greater demand than C/C++. In
fact, the rate of demand for the languages has changed dramatically.
The change in demand for XML outstrips all other languages with
Perl, the glue for Internet applications, an unexpected second. Perls
importance can be seen as a growth in tools for infrastructure
implementation. Note also that one of the few increasing changes in
demand is in Python, another glue language.
Figure #1
Figure #2
In terms of demand, you already know most of the big players
Windows and Unix. The surprise occurs in the changing demand for
platform-specific skills. The big winner here is Linux followed
closely by Windows 2000. This says that there is a pent up demand
for Linux skills that is outstripping all other platforms. Only
Windows 2000 comes close, but that is to be expected, based on the
huge installed base of Windows software already in the market and
which firms need to upgrade.

Figure #3

Figure #4
The second clear fact is that Solaris dominates the Unix
marketplace and demand for expertise in it continues to grow, but at
half the speed of Windows 2000 and Linux.
The source for these charts come from Internet online job sites.
Thus, these numbers represent demand for new hires, not who are
currently employed doing what. However, some estimates can be made
from these figures, but that is a subject of another article.
These online sites are searched by keywords and the number of hits
are recorded. Often, defining a skill is fuzzy. For example, the
category Windows 98 is actually the Boolean expression Windows98
or Win98 or 98. Thus, an ad asking for experience with W98
would be missed. Since getting some measurements is better than
nothing, we compute raw demand for a skill. I often skip this chart
because it is not very interestingjust a series of numbers
without much context.
The first interesting chart is the Normalized Demand set of
charts. This is the demand for a particular skill divided by the
demand for all skills in that category. The second chart of interest
is the change in demand for a skill since the last time we did a
measurement of that skill. You can think of these charts as Skill
Velocity Charts how fast is the demand for the skill changing.
The last chart is the Skill Acceleration chart that represents the
change in changing demand over the last period we are measuring. It
measures how fast demand is changing for each skill set within a
category.
|