|« Preprocessor Code Generation||Type Lists »|
For this entry, I would like to introduce the
type_traits header file. This file contains utility templates that greatly simplify work when writing template-based libraries. This is especially true for libraries that employ template meta-programming. The header file is available with C++ TR1 and above. This library provides tools to identify types, their qualifying properties and even peel-off properties one-by-one programmatically at compile-time. There are also transformation meta-functions that provide basic meta-programming operations such as a compile-time conditional.
The definitions in
type_traits will save us a lot of time implementing Alchemy. As I introduce some of the tools that are available in the header, I will also demonstrate how these operations can be implemented. This will help you understand how to construct variations of the same type of solution when applying it in a different context. As an example of this, I will create a construct that behaves similarly to the tertiary operator, but is evaluated at compile-time.
Types and Values
Creating C++ template meta-programs are essentially functional programs. Functional programs compute with mathematical expressions. You will receive the same result each time you call a function with a specific set of parameters. As expressions are declarative, state and mutable data are generally avoided. Meta-programs are structured in a way to require the compiler to calculate the results of the expressions as part of compilation. The two constructs that we have to work with are integer constants, to hold calculation results, and types, which we can use like function calls.
Define a Value
Meta-program constants are declared and initialized statically. Therefore, these values are limited to integer-types. We cannot use string literals or floating-points because these types of static constants cannot be initialized in place with the class or struct definition. Sometimes you will see code declared with enumerations. I believe this is to prevent meta-programming objects from using space. It is possible for users to take the address of static constants, and if this occurs, the compiler must allocate space for the constant, even if the object is never instantiated. It is not possible to take the address of an enumeration declaration. Therefore no memory must be allocated when an enumeration is used.
Since I started using
type_traits, I don't worry about it so much. I use the
integral_constant template to define values. By convention, values are given the name
value. This is important in generic programming to allow the object development to remain loosely coupled and independent of the design of other objects. The example below demonstrates how the
integral_constant is typically used. Please note that all of these constructs live in the
std:: namespace, which I will be omitting in these examples.
Call (Instantiate) our meta-function:
All that is required to implement the
integral_constant, is a definition of a static constant in the template struct. Structs are generally used because of their default public member access. Here is the most basic implementation:
Yes it's that simple. The implementation in the C++ Standard Library goes a little bit further for convenience. Just as with the STL Containers,
typedefs are created for the
type of the object. There is also a value conversion operator to implicitly convert the object to the value_type. Here is the complete implementation:
typedefs have been created to reduce the amount of code required to perform Boolean logic with meta-programs:
With meta-programming there is only one way to define a variable, and there are many ways to create decision making logic. Let's start with one that is very useful for making decisions.
This template allows you to test if two types are the same type.
The compiler always looks for the best fit. That way, when multiple templates would be suitable, only the best match will be selected, if that is possible. In this case, the best match for when both types are the exact same, is our specialization that indicates
It's time to define a branching construct to enable us to make choices based on type. The
conditional template is the moral equivalent of the if-statement for imperative C++.
Applying These Techniques
I have just demonstrated how three of the constructs defined in the
type_traits header could be implemented. The techniques used to implement these constructs are used repeatedly to create solutions for evermore complex problems. I would like to demonstrate a construct that I use quite often in my own code, which is both built upon the templates I just discussed, and implemented with the same techniques used to implement those templates.
conditional template will define a type based on the result a Boolean expression, I commonly want to conditionally define a value based on the result of a Boolean expression. Therefore, I implemented the
value_if template. This makes use of the
integral_constant template and a similar implementation as was used to create the
conditional template. This gives me another tool to simplify the complex parametric expressions that I often encounter.
I just introduced you to the
type_traits header in C++. If you have not yet discovered this header, you should check it out. It can be very useful, even if you are not creating template meta-programs. Here is a reference link to the header from cppreference.com[^].
With the basic constructs that I introduced in this entry, I should now be able to create more sophisticated ways to interact with the Typelist[^] that I previously discussed for Alchemy. With the simple techniques used above, we should be able to implement template expressions that will query a Typelist type by index, get the size of the type at an index, and similarly calculate the offset in bytes from the beginning of the Typelist. The offset will be one of the most important pieces of information to know for the Alchemy implementation.