Thursday, May 8, 2008

Imperative C++ metaprogramming

C++ template metaprogramming is fundamentally a functional business, as it deals with immutable data like types and numeric compile-time constants. How can we go about introducing some degree of imperativeness in C++ metaprogramming? The one thing that distinguishes imperative programming is the existence of variables, entities whose associated value can change over time. In a sense, a variable can be seen as a function of time or the program counter; this can be modeled as the following C++ class template:

template<std::size_t Id,std::size_t T>
struct var
{
typedef typename var<Id,T-1>::type type;
};
var implements an Id-indexed family of variables {vId} dependent on the time argument T. By the way var is defined, the value of vId at time t + 1 is the same it had at time t; this is expected, since a variable does not change value over time unless the program explicitly does so. Setting a new value for a variable in our model can only be done by partially specializing var as follows:
// set var(0) to 5 starting at time 69
template<>
struct var<0,69>
{
typedef boost::mpl::int_<5> type;
};
(We are using MPL constants here.) The problem with this approach is that classical template metaprogramming cannot be used to perform a variable setting operation: all that template metaprogramming reduces to is template instantiation, that cannot possibly result in a non-predefined partial specialization. So we need to step back and use preprocessor metaprogramming to effectively use var:
template<typename T> struct unparens_helper;
template<typename T> struct unparens_helper<void(T)>{typedef T type;};
#define UNPARENTHESIZE(f) unparens_helper<void f>::type

#define VAR(Id) var<Id,__COUNTER__>::type
#define LET(Id,x) template<> struct var<Id,__COUNTER__>:UNPARENTHESIZE(x){};
The code above uses the MSVC-specific macro __COUNTER__ to easily simulate a program counter. As the second argument of LET is likely to contain commas, it is required that it be enclosed in parentheses so as not to fool the preprocessor: UNPARENTHESIZE implements some magic to internally remove these parentheses. The computation
v0 = 2;
v1 = 3;
v1 = v0 + v1;
is implemented like this:
LET(0,(boost::mpl::int_<2>))
LET(1,(boost::mpl::int_<3>))
LET(1,(boost::mpl::plus<VAR(0),VAR(1)>)
A complete program exercising these ideas is provided. This method of implementing imperativeness inside C++ metaprogramming is less than ideal as it forces us to do the non-functional part in the preprocessor realm; this makes it impossible, for instance, to define an MPL metafunction whose implementation is done imperatively.

No comments :

Post a Comment