C++ pointer template parameters are weird

I was browsing through the C++ standard the other day (as you do), and I was surprised to learn that the following are all legal as non-type template parameters:

  • integral or enumeration type
  • pointer to object or pointer to function
  • reference to object or reference to function
  • pointer to member

If you’ve ever done metaprogramming then you’ll certainly have used integral types as template parameters. And I’ve already come across pointers to member as template parameters in possibly my gnarliest typedef ever, which for the record was something like:

typedef
template< typename T>
boost::variant<T::* int, T::* string, T::* double, T::* bool>
   any_member;

You can use this type to refer to any (modulo ramming enough different types in the variant<> list) member on any type at compile time. I had this rigged up into rather a nice compile-time ORM system, where you could define a type and then declare a static instance of

std::map<string, any_member<T> >

that maps strings to members at compile time. You can then have generic code that pulls data out of a database and then assigns it to instances of a the class. So much of it is done at compile time that it was actually pretty reliable code for something so hairy.

But I digress. The point is, pointers to members act in this context quite a lot like enumerated types: there’s only a finite (usually very small) number of valid values that is obviously determinable at compile time. Even integral types, in the contexts where they are used, are finite and typically small. Pointers to objects, on the other hand, usually don’t exist until run time and can take any one of an effectively infinite range of values at run time.

So the declaration

template< int * Foo >
class Bar {
  public:
    void doStuff() {
        std::cout << *Foo << std::endl;
    }
}

is perfectly legal. But how can it be used? Remember, each specialisation of Bar is not just a different object but a wholly different class. On the face of it, this might seem to allow creation of types at run time, which is obviously impossible.

It turns out that they’ve thought of that. When you try to specialise your template it all goes wrong:

int main( int argc, char ** argv ) {
    int * ptr = new int(42);
    Bar<ptr> myObj;
}

This is rejected by g++ with:

error: 'ptr' is not a valid template argument of type 'int*' because 'ptr' is a variable, not the address of a variable

Full marks to g++ for a good error message here. This immediately prevents the possibility that we can generate an unlimited number of types at run time, since we have only a finite number of variables defined in our program, fixed at compile time. But what about the address of a stack variable? Taking the address of the stack variable doesn’t return a value fixed at compile time, because it depends how deep we are in the stack when we do it:

void confuseTheCompiler() {
    int someVar;
    Bar< &someVar > myObject;
}

Fortunately, they’ve thought of this as well:

error: '& someVar' is not a valid template argument of type 'int*' because 'someVar' does not have external linkage

This is made explicit, as you would expect, in the C++ standard. It’s in 14.3.2 clause 1:

A template-argument for a non-type, non-template template-parameter shall be one of:

  • an integral constant-expression of integral or enumeration type; or
  • the name of a non-type template-parameter; or
  • the address of an object or function with external linkage, including function templates and function
    template-ids but excluding non-static class members, expressed as & id-expression where the & is
    optional if the name refers to a function or array, or if the corresponding template-parameter is a reference;
    or
  • a pointer to member expressed as described in 5.3.1

So if we restrict ourselves to variables with external linkage then we’re back to the case where we have a fixed number of possible values at compile time, and everything in the garden is lovely.

I’m not sure if this would ever be useful. I came up with this toy example:

#include <iostream>
#include <string>
 
using namespace std;
 
template< int * foo >
class VariableNamer {
public:
	static string name;
};
 
extern int baz;
 
int baz = 42;
 
template<>
string VariableNamer< &baz >::name = "baz";
 
int main( int argc, char ** argv ) {
	cout << VariableNamer<&baz>::name << endl;
}

It enables you to associate one and only one name with a variable that has external linkage, at compile time. If you forget to assign a name for the variable you’ll get a compile-time error, which I suppose makes it better than having a global

std::map< int *, std::string> variable_names;

I’m sure with a little extra finesse it could be modified to assign a name to any type of variable, not just ints.

One thought on “C++ pointer template parameters are weird

  1. Jack V

    I’m not as good at this as I should be, but possibilities that occur to me would be:

    Using a static object to describe the type desired (Like, using a size object to define a fixed-size 2-d array, instead of giving two int parameters (?). Or using a string to give a class type a name at compile type, if for some reason the built in name or a static variable don’t work (?)).

    Having a class which relates to a specific object (eg. a multi-gigabyte-size class which you allocate just one one and have some classes which only ever refer to that particular instantiation (?)).

    Although I don’t know if they’re even valid

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *