Templates and the C++ syntax tree
In my last few posts I’ve been writing about C++0x templates. Specifically I’ve been talking about how to make template classes easier to read by allowing more-imperative/less-declarative and more-iterative/less-recursive class definitions. I’ve talked about extending the … packing operator and adding code-generating scripting to the compiler.
The C++ compiler already has “scripting” of sorts, in the form of the preprocessor. But this is very limited, a declarative language without recursion (or iteration), at least not without very clever hackery (see the Boost Preprocessor BOOST_PP_.. macros and my earlier post about using BOOST_PP_.. macros).
The usual way of generating C++ code is with templates, which are declarative and allow full recursion. Templates should be all you need, but they are surprisingly difficult to use. This is partly due to syntax, but mainly due to the fact that templates only deal with large syntactic sub-trees (class and function definitions). You cannot use a template if you just want to generate an expression or an argument list. Also templates only have integers and classes (structs) available as data structures (although MPL helps here a little), and that template selection (pattern matching) is primitive (you cannot easily match patterns buried inside structures or lists).
In a previous post I talk about how convenient it would be if we could use the … packing operator to generate member variables for the tuple class.
/* fantasy code -- will not work */
template< typename ... TYPEs >
class
tuple
{
// Member variables - not a legal use of ...
private:
TYPEs ...
values;
/* .. etc .. */
};
It’d be nice if you could write a meta-function using templates to generate the sequence of member-declarations necessary (the BNF non-terminal for the sequence is called member-specification).
/* fantasy code -- will not work */
template< typename ... TYPEs >
struct
generate_member_variables
{
// Generate member declarations like this:
// TYPE0 member_0 ;
// TYPE1 member_1 ;
// TYPE2 member_2 ;
// .. etc ..
typedef
std::grammar::
generate_member_specification_from_types
< 0
, "member_"
, std::grammar::list< TYPEs ... >
>::eval
eval;
};
template< typename ... TYPEs >
class
tuple
{
private:
// The compiler will change the following struct
// into a sequence of member variables.
generate_member_variables< TYPEs ... >::eval;
/* .. etc .. */
};
To make this work you’d need a library of meta-functions (templated structs) and classes to represent parts of the C++ syntax tree. The compiler would also have to recognize these snippets of syntax tree and compile them into the program like they were a normal part of the token stream. (You’d also need to be able to manipulate lists, tokens, literals, identifiers, and strings at compile-time.)
In the above example we assume a meta-function library that lives in namespace std::grammar and can build a struct that the compiler will recognize as a sequence of member variable declarations.
With these changes you could specify member variables something like this.
/* fantasy code -- will not work */
using std::grammar::member_specification;
using std::grammar::member_declarator;
using std::grammar::type_name;
using std::grammar::identifier;
using std::grammar::list;
struct
my_struct
{
// example 1
// generates these member vars:
// int m_id;
// double m_price;
// char* p_name;
codegen typename
member_specification
< list< int, double, char* >
, list< "m_id", "m_price", "p_name" >
>::eval;
// example 2
// generates these member vars:
// my_class1 member_var1;
// my_class2 member_var2;
codegen typename
member_specification
< list
< member_declarator
< type_name< "my_class1" >
, identifier< "member_var1" >
>
, member_declarator
< type_name< "my_class2" >
, identifier< "member_var2" >
>
>
>::eval;
/* .. etc .. */
};
This example introduces the keyword codegen which tells the compiler to expect a special type. A little like typedef, ‘cept not really. When the compiler sees codegen it knows treat the type as a C++ syntax tree and expand it into inline code.
The C++ compiler could be much more code-generation friendly. Defining meta-classes and meta-functions to represent and manipulate parsed C++ code is a good first step, along with adding a way to inline these syntax-tree objects into the code. Boost MPL provides a good of example of how those meta-objects can be defined.
This would let you build small parts of the syntax tree, like parameter lists, expressions, and initialization lists that you could reuse. These in turn would make it easier to generate larger class and function templates.
Comments
Leave a Reply