THE BOOST TYPEOF LIBRARY

Motivation

Why typeof?

Why typeof emulation?

Example

The 3 participating parties

Features supported

Registration

Usage

Limitations

Acknowledgements

 

Motivation

Why typeof?

Today many template libraries supply object generators to simplify object creation by utilizing the C++ template argument deduction facility.  Consider std::pair.  In order to instantiate this class template and create a temporary object of this instantiation, one has to supply both type parameters and value parameters:

 

std::pair<int, double>(5, 3.14159);

 

To avoid this duplication, STL supplies the std::make_pair object generator.  When it is used, the types of template parameters are deduced from supplied function arguments:

 

std::make_pair(5, 3.14159);

 

For the temporary objects it is enough.  However, when a named object needs to be allocated, the problem appears again:

 

std::pair<int, double> p(5, 3.14159);

 

The object generator no longer helps:

 

std::pair<int, double> p = std::make_pair(5, 3.14159);

 

It would be nice to deduce the type of the object (on the left) from the expression it is initialized with (on the right), but the current C++ syntax doesn’t allow for this.

 

The above example demonstrates the essence of the problem but doesn’t demonstrate its scale.  Many libraries, especially expression template libraries, create objects of really complicated types, and go a long way to hide this complexity behind object generators.  Consider a nit Boost.Lambda functor:

 

_1 > 15 && _2 < 20

 

If one wanted to allocate a named copy of such an innocently looking functor, she would have to specify something like this:

 

lambda_functor<

      lambda_functor_base<

            logical_action<and_action>,

            tuple<

                  lambda_functor<

                        lambda_functor_base<

                              relational_action<greater_action>,

                              tuple<

                                    lambda_functor<placeholder<1> >,

                                    int const

                              >

                        >

                  >,

                  lambda_functor<

                        lambda_functor_base<

                              relational_action<less_action>,

                              tuple<

                                    lambda_functor<placeholder<2> >,

                                    int const

                              >

                        >

                  >

            >

      >

>

f = _1 > 15 && _2 < 20;

 

Not exactly elegant.  To solve this problem, the C++ standard committee is considering a few additions to the standard language, such as typeof/decltype and auto (see http://www.osl.iu.edu/~jajarvi/publications/papers/decltype_n1478.pdf).

 

The typeof operator (or decltype, which is a slightly different flavor of typeof) allows one to determine the type of an expression at compile time.  Using typeof, the above example can be simplified drastically:

 

typeof(_1 > 15 && _2 < 20) f = _1 > 15 && _2 < 20;

 

Much better, but there still is duplication.  The auto type solves the rest of the problem:

 

auto f = _1 > 15 && _2 < 20; 

Why typeof emulation?

According to a rough estimate, at the time of this writing (June 2004) the introduction of the typeof, auto, etc., into the C++ standard is not going to happen at least within a year.  Even after it’s done, some time still has to pass before most compilers implement this feature.  But even after that, there always are legacy compilers to support (for example now, in 2004, many people are still using VC6, long after VC7.x became available).

 

Considering extreme usefulness of the feature right now, it seems to make sense to try to emulate it at a library level.  Such emulation will inevitably have major drawbacks, such as additional responsibilities put on the library users, slower compiles, etc., but in the end, depending on the context, the benefits will likely outweigh.

 

Boost.Typeof implements such emulation utilizing template and preprocessor meta-programming facilities provided by Boost MPL and Preprocessor libraries.  

Example

Here is how one could re-write the above examples using BOOST_AUTO:

 

Boost.Typeof

typeof

BOOST_AUTO(p, std::make_pair(5, 3.14159)); 

auto p = std::make_pair(5, 3.14159);

BOOST_AUTO(f, _1 > 15 && _2 < 20);

auto f = _1 > 15 && _2 < 20; 

 

Looks like a pretty good approximation.  However, there is some cost.  Since, unlike the compiler, the library doesn’t know much about types the expression is combined of, the user must help by providing some information.  For the first example something like following has to be specified:

 

#include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()

BOOST_TYPEOF_REGISTER_TEMPLATE(std::pair, 2);

 

The first line:

 

#include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()

 

does pretty much what it says: increments the BOOST_TYPEOF_REGISTRATION_GROUP symbolic constant.  This constant is used by all registration macros, and must be defined to some unique integer in every source or header file that contains any registration.  Incrementing it insures that it is, indeed, unique.

 

The second line registers std::pair as a template with two parameters. 

 

Once any types and templates are registered, the library can handle any combination of those.  Since all the fundamental types are pre-registered by the typeof library, the above registration makes it possible to also write something like this:

 

BOOST_AUTO(p, 

  std::make_pair(

    std::make_pair(5, 3.14159),

    std::make_pair(2.71828, 6)));

 

The more complicated Lambda example above requires some more registration:

 

#include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()

 

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::tuples::tuple, 2);

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::lambda::lambda_functor, 1);

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::lambda::lambda_functor_base, 2);

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::lambda::relational_action, 1);

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::lambda::logical_action, 1);

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::lambda::other_action, 1);

BOOST_TYPEOF_REGISTER_TYPE(boost::lambda::greater_action);

BOOST_TYPEOF_REGISTER_TYPE(boost::lambda::less_action);

BOOST_TYPEOF_REGISTER_TYPE(boost::lambda::and_action);

BOOST_TYPEOF_REGISTER_TYPE(boost::lambda::placeholder<1>);

BOOST_TYPEOF_REGISTER_TYPE(boost::lambda::placeholder<2>);

 

The 3 participating parties

It may seem that the price for the ability to discover the expression’s type is too high: rather large amount of registration is required.  However note that all of the above registration is done only once, and after that, any combination of the registered types and templates would be handled.  Moreover, this registration is typically done not by the end-user, but rather by a layer on top of some library (in this example -- Boost.Lambda).

 

When thinking about this, it’s helpful to consider three parties: the typeof facility, the library (probably built on expression templates principle), and the end-user.  The typeof facility is responsible for registering fundamental types.  The library can register its own types and templates.

 

In the best-case scenario, if the expressions always consist of only fundamental types and library-defined types and templates, a library author can achieve the impression that the typeof is natively supported for her library.  On the other hand, the more often expressions contain user-defined types, the more responsibility is put on the end-user, and therefore the less attractive this approach becomes. 

 

Thus, the ratio of user-defined types in the expressions should be the main factor to consider when deciding whether or not to apply this facility.

Features supported

The Boost.Typeof  library pre-registers fundamental types.  For these types, and for any other types/templates defined by the user library or end-user, any combination of the following is supported:

 

§         Pointers;

§         References (except top-level);

§         Consts (except top-level);

§         Arrays;

§         Function pointers;

§         Pointers to member functions;

§         Pointers to data members.

 

For example the following type:

 

int& (*)(const char*, double[5], void(*)(short))

 

is supported right away, and something like:

 

void (MyClass::*)(int MyClass::*, MyClass[10]) const

 

is supported provided MyClass is registered.

Registration

Registration may be done by either a library author or an end-user.  The purpose of registration is to create a pair of (partial) template specializations for a given type or template.  One of such specializations will encode the type into an MPL sequence of integers, and the other – decode it.

 

Three macros are provided to simplify the registration:

 

§         BOOST_TYPEOF_REGISTER_TYPE(Type);

§         BOOST_TYPEOF_REGISTER_TEMPLATE(Name, nParams);

§         BOOST_TYPEOF_REGISTER_TEMPLATE_X(Name, ParamSeq);

 

All three rely on the integer value defined by BOOST_TYPEOF_REGISTRATION_GROUP.  This value should be unique for each file, since it is used in conjunction with __LINE__ preprocessor macro to generate unique integer identifiers to assign to types and templates.  To establish a unique value for BOOST_TYPEOF_REGISTRATION_GROUP, the following line should be specified before the actual registration is done:

 

#include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()

 

A type is registered by simply supplying its name to the BOOST_TYPEOF_REGISTER_TYPE macro. 

 

Two macros are provided for template registration.  First of them, BOOST_TYPEOF_REGISTER_TEMPLATE, accepts template name and the number of template parameters.  Only templates with type parameters are supported by this macro.  If one wants to register a template with integral parameters, BOOST_TYPEOF_REGISTER_TEMPLATE_X macro should be used.

 

The BOOST_TYPEOF_REGISTER_TEMPLATE_X macro accepts a template name and a preprocessor sequence describing template parameters, where each parameter should be described as one of the following:

 

§         class

§         typename

§         [unsigned] char

§         [unsigned] short

§         [unsigned] int

§         [unsigned] long

§         unsigned

§         bool

 

For example:

 

BOOST_TYPEOF_REGISTER_TEMPLATE_X(foo, (class)(unsigned int)(bool));

 

A template is encoded by combining its integer identifier with results of encoding its parameters.  If a template has default parameters, it might be a good idea to register it more than once:

 

BOOST_TYPEOF_REGISTER_TEMPLATE(std::set, 3);

BOOST_TYPEOF_REGISTER_TEMPLATE(std::set, 1);

 

The above would result in two different pairs of template specializations, the latter one – more specialized.  Consider std::set<int[, std::less<int>, std::allocator<int> ]>.  In general this type would be encoded by 6 integers:

 

std::set, int, std::less, int, std::allocator, int

 

However, because the second registration was done, in this particular case only two integers would be required, thus saving on default parameters:

 

std::set, int

Usage

The usage is pretty straightforward.  BOOST_TYPEOF is used to discover the type of the expression, like following:

 

BOOST_TYPEOF(f) f1; // compare with: typeof(f) f1;

f1 = f;


BOOST_AUTO is used to allocate a named object initialized with a given expression such as:

 

BOOST_AUTO(f, _1 > 15 && _2 < 20); // compare with: auto f = _1 > 15 && _2 < 20;

 

A (const) reference can be allocated like this:

 

const int& foo()

{

static int n;

return n;

}

 

//later:

BOOST_AUTO(const& var, foo()); // compare with: auto const& var = foo();

 

BOOST_TYPEOF_TPL and BOOST_AUTO_TPL, accordingly, should be used in templates when the expression depends on template parameters.  These macros take care of adding or avoiding the keyword “typename”, depending on whether the native typeof support or emulation is being used.

 

Another macro, BOOST_TYPEOF_PRESERVE_LVALUE, is an attempt to emulate the “decltype”.  Its rules, however, are directly built on the rules of binding a reference, and therefore differ from the ones of real “decltype”:

 

expr

decltype

BOOST_TYPEOF_PRESERVE_LVALUE

int i;

int

int&

const int ci = 0;

const int

const int&

int& ri = i

int&

int&

const int& cri = ci;

const int&

const int&

int foo();

int

int

const int foo();

const int

const int&

int& foo();

int&

int&

const int& foo();

const int&

const int&

21

int

int

int(21)

int

int


Of all these rules, the highlighted one seems to be the most unfortunate.

Limits and limitations

§         The number of nodes in the expression determines the size of an MPL vector required to encode the type.  It is limited by BOOST_TYPEOF_LIMIT_SIZE (by default 50), which is, in turn, limited by the maximum number of template parameters supported by the compiler.

§         The number of function parameters supported is limited by BOOST_TYPEOF_FUN_PARAMS_SUPPORTED, by default 10.  This applies to function pointers and to pointers to member functions.

Acknowledgements

The following people provided support, gave valuable comments, or in any other way contributed to the library development (this list is under construction):

 

§         David Abrahams;

§         Joel de Guzman;

 

Copyright © 2004 Arkadiy Vertleyb