...one of the most highly
regarded and expertly designed C++ library projects in the
world. — Herb Sutter and Andrei
The elements of a type about which a template metaprogrammer might be interested in finding out at compile time are:
These are some of the compile-time questions which the TTI library answers. It does this by creating metafunctions, which can be used at compile-time, using C++ macros. Each of the metafunctions created returns a compile time constant bool value which answers one of the above questions at compile time. When the particular element above exists the value is 'true', or more precisely boost::mpl::true_, while if the element does not exist the value is 'false', or more precisely boost::mpl::false_. In either case the type of this value is boost::mpl::bool_.
This constant bool value, in the terminology of the Boost MPL library, is called an 'integral constant wrapper' and the metafunction generated is called a 'numerical metafunction'. The results from calling the metafunction can be passed to other metafunctions for type selection, the most popular of these being the boolean-valued operators in the Boost MPL library.
All of the questions above attempt to find an answer about an inner element with a particular name. In order to do this using template metaprogramming, macros are used so that the name of the inner element can be passed to the macro. The macro will then generate an appropriate metafunction, which the template metaprogrammer can then use to introspect the information that is needed. The name itself of the inner element is always passed to the macro as a macro parameter, but other macro parameters may also be needed in some cases.
All of the macros start with the prefix
create their metafunctions as class templates in whatever scope the user invokes
the macro, and come in two forms:
BOOST_TTI_prefix is removed, the rest of the macro name is changed to lower case, and an underscore ( '_' ) followed by the 'name' is appended. As an example, for the macro
BOOST_TTI_HAS_TYPE(MyType)the name of the metafunction is
has_type_MyTypeand it will look for an inner type called 'MyType'.
BOOST_TTI_TRAIT_and a 'trait' name is passed as the first parameter, with the 'name' of the inner element as the second parameter. The 'trait' name serves solely to completely name the metafunction in whatever scope the macro is invoked. As an example, for the macro
BOOST_TTI_TRAIT_HAS_TYPE(MyTrait,MyType)the name of the metafunction is
MyTraitand it will look for an inner type called
Every macro metafunction has a simple macro form and a corresponding complex macro form. Once either of these two macro forms are used for a particular type of inner element, the corresponding macro metafunction works exactly the same way and has the exact same functionality.
In the succeeding documentation all macro metafunctions will be referred by their simple form name, but remember that the complex form can always be used instead. The complex form is useful whenever using the simple form could create a duplicate name in the same name space, thereby violating the C++ one definition rule.
For the simple macro form, even though it is fairly easy to remember the algorithm by which the generated metafunction is named, TTI also provides, for each macro metafunction, a corresponding 'naming' macro which the end-user can use and whose sole purpose is to expand to the metafunction name. The naming macro for each macro metafunction has the form: 'corresponding-macro'_GEN(name).
As an example, BOOST_TTI_HAS_TYPE(MyType) creates a metafunction which looks for a nested type called 'MyType' within some enclosing type. The name of the metafunction generated, given our rule above is 'has_type_MyType'. A corresponding macro called BOOST_TTI_HAS_TYPE_GEN, invoked as BOOST_TTI_HAS_TYPE_GEN(MyType) in our example, expands to the same 'has_type_MyType' name. These name generating macros, for each of the metafunction generating macros, are purely a convenience for end-users who find using them easier than remembering the name-generating rule given above.
Because having a double underscore ( __ ) in a name is reserved by the C++ implementation, creating C++ identifiers with double underscores should be avoided by the end-user. When using a TTI macro to generate a metafunction using the simple macro form, TTI appends a single underscore to the macro name preceding the name of the element that is being introspected. The reason for doing this is because Boost discourages as non-portable C++ identifiers with mixed case letters and the underscore then becomes the normal way to separate parts of an identifier name so that it looks understandable. Because of this decision to use the underscore to generate the metafunction name from the macro name, any inner element starting with an underscore will cause the identifier for the metafunction name being generated to contain a double underscore.
A rule to avoid this problem is:
Furthermore because TTI often generates not only a metafunction for the end-user to use but some supporting detail metafunctions whose identifier, for reasons of programming clarity, is the same as the metafunction with further letters appended to it separated by an underscore, another rule is:
Following these two simple rules will avoid names with double underscores being generated by TTI.
When the end-user uses the TTI macros to generate a metafunction for introspecting an inner element of a particular type, that metafunction can be re-used with any combination of valid template parameters which involve the same type of inner element of a particular name.
As one example of this let's consider two different user-defined types called 'AType' and 'BType', for each of which we want to determine whether an inner type called 'InnerType' exists. For both these types we need only generate a single macro metafunction in the current scope by using:
We now have a metafunction, which is a C++ class template, in the current scope whose C++ identifier is 'has_type_InnerType'. We can use this same metafunction to introspect the existence of the nested type 'InnerType' in either 'AType' or 'BType' at compile time. Although the syntax for doing this has no yet been explained, I will give it here so that you can see how 'has_type_InnerType' is reused:
As another example of metafunction reuse let's consider a single user-defined type, called 'CType', for which we want to determine if it has either of two overloaded member functions with the same name of 'AMemberFunction' but with the different function signatures of 'int (int)' and 'double (long)'. For both these member functions we need only generate a single macro metafunction in the current scope by using:
We now have a metafunction, which is a C++ class template, in the current scope whose C++ identifier is 'has_member_function_AMemberFunction'. We can use this same metafunction to introspect the existence of the member function 'AMemberFunction' with either the function signature of 'int (int)' or 'double (long)' in 'CType' at compile time. Although the syntax for doing this has no yet been explained, I will give it here so that you can see how 'has_type_InnerType' is reused:
These are just two examples of the ways a particular macro metafunction can be reused. The two 'constants' when generating a macro metafunction are the 'name' and 'type of inner element'. Once the macro metafunction for a particular name and inner element type has been generated, it can be reused for introspecting the inner element(s) of any enclosing type which correspond to that name and inner element type.
The TTI macro metafunctions are generating directly in the enclosing scope in which the corresponding macro is invoked. This can be any C++ scope in which a class template can be specified. Within this enclosing scope the name of the metafunction being generated must be unique or else a C++ ODR ( One Definition Rule ) violation will occur. This is extremely important to remember, especially when the enclosing scope can occur in more than one translation unit, which is the case for namespaces and the global scope.
Because of ODR, and the way that the simple macro form metafunction name is directly dependent on the inner element and name of the element being introspected, it is the responsibility of the programmer, using the TTI macros to generate metafunctions, to avoid ODR within a module ( application or library ). There are a few general methods for doing this:
For anyone using TTI in a library which others will eventually use, it is important to generate metafunction names which are unique to that library.
TTI also reserves not only the name generated by the macro metafunction for its use but also any C++ identifier sequence which begins with that name. In other words if the metafunction being generated by TTI is named 'MyMetafunction' using the complex macro form, do not create any C++ construct with an identifier starting with 'MyMetaFunction', such as 'MyMetaFunction_Enumeration' or 'MyMetaFunctionHelper' in the same scope. All names starting with the metafunction name in the current scope should be considered out of bounds for the programmer using TTI.