Tuesday, April 11, 2006

Determining if a type is a pointer at compile time

http://doc.trolltech.com/4.1/qlist.html:

"QList is represented as an array of pointers to items. (Exceptionally, if T is a pointer type, a basic type of the size of a pointer, or one of Qt's shared classes, QList stores the item directly in the pointer.)"

So a QList<QRect> is going to be an array of QRect * but QList<QWidget *> will be an array of QWidget * - not QWidget **. But how can we determine whether a type is a pointer at compile time?

Update: This describes the method used in Qt for compilers lacking support for partial template specialisation. If you have partial specialisation, it can be done in a very straightforward way (see blog comments).

Well it seems to be done by abusing our favourite language. QList uses the QTypeInfo class - the interesting bits being:


template <typename T> char QTypeInfoHelper(T*(*)());
void* QTypeInfoHelper(...);

template <typename T>
class QTypeInfo
{
public:
enum {
isPointer = (1 == sizeof(QTypeInfoHelper((T(*)())0))),


int *



Now, consider the case of QTypeInfo<int *>. the last line expands to:

isPointer = (1 == sizeof(QTypeInfoHelper( (int * (*) ()) 0))).

Now I've added some spaces so that it can actually be read. It's saying, pass NULL - casted to a pointer to a function that accepts nothing and returns an int * - to one of the QTypeInfo functions.

But to which 1 of the 2 overloads? Well, the C++ rule for resolving overloaded function calls is to select the function with the most specific matching argument types, assuming no ambiguity. In this case, our function call matches this candidate:

template <typename T> char QTypeInfoHelper(T * (*) ());

because T is int. Now you won't actually find QTypeInfoHelper defined anywhere because all sizeof is interested in is the size of the return value of the function - it doesn't actually execute it. Now, the return type is a char, which is of size 1, therefore isPointer is true.

int



Now we look at a non-pointer type, int. The tricky line expands to:

isPointer = (1 == sizeof(QTypeInfoHelper( (int (*) ()) 0))).

That function pointer argument type accepts nothing and returns an int. It's not going to match the same overload because it's looking for a pointer (the T *):

template <typename T> char QTypeInfoHelper(T * (*) ());

And int is no pointer. Therefore, it can only match the catch-all overload:

void* QTypeInfoHelper(...);

which returns a void* and guess what:

isPointer = (1 == sizeof(void *)).

is false (unless on an 8-bit machine :)). Therefore, int is correctly detected as not a pointer.

Conclusion



QTypeInfo manages to determine whether a type is a pointer without:


  1. actually invoking a function (by merely checking the size of the return type)

  2. ever constructing an element of type T (which might otherwise cause side effects)

4 comments:

Stefan Nikolaus said...

Hi,

some nitpickery: For pointers and basic types separate templates exists. I.e.
int* uses def. at qglobal.h:1366
int uses def. at qglobal.h:1453

Bye,
Stefan

Alexander Petrov said...

You should look at Boost.TypeTraits library: all is implemented, tested and ported to every compiler

http://www.boost.org/doc/html/boost_typetraits/reference.html#boost_typetraits.is_pointer

Clarence Dang said...

> For pointers and basic types separate
> templates exists. I.e.
> int* uses def. at qglobal.h:1366

Yes, I missed the simpler way if doing it #ifndef QT_NO_PARTIAL_TEMPLATE_SPECIALIZATION:


template <typename T>
class QTypeInfo
{
enum {
isPointer = false,

[...]

template <ypename T>
class QTypeInfo<T*>
{
enum {
isPointer = true,


> int uses def. at qglobal.h:1453

s/int/QRect then :)

Vladimir Prus said...

It should be noted that the "two functions" approach is more powerfull then partial template specialization. Same Boost.TypeTraits has traits called is_convertible, which can be used to test if type D is convertible to type B. It can be implemented like this:

char overload(B);
void* overload(...);
D d;
.... sizeof(overload(d));

that's something that can't be done using partial specialization.