mirror of
https://github.com/boostorg/tti.git
synced 2026-01-26 07:02:14 +00:00
194 lines
8.5 KiB
Plaintext
194 lines
8.5 KiB
Plaintext
[section:tti_nested_type Nested Types]
|
|
|
|
[heading The problem]
|
|
|
|
The goal of the TTI library is never to produce a compiler error by just
|
|
using the functionality in the library, whether it is invoking its function-like macros
|
|
or instantiating the macro metafunctions created by them, and whether the inner element exists
|
|
or not. In this sense The TTI library macros for introspecting an enclosing type for
|
|
an inner element work very well. But there is one exception to this general case.
|
|
That exception is the crux of the discussion regarding nested types which follows.
|
|
|
|
The metafunctions generated by the TTI macros all work with types, whether in specifying
|
|
an enclosing type or in specifying the type of some inner element, which may also involve
|
|
types in the signature of that element, such as a parameter or return type of a function. The C++ notation for
|
|
a nested type, given an enclosing type 'T' and an inner type 'InnerType', is 'T::InnerType'. If either
|
|
the enclosing type 'T' does not exist, or the inner type 'InnerType' does not exist within 'T',
|
|
the expression 'T::InnerType' will give a compiler error if we attempt to use it in our template
|
|
instantiation of one of TTI's macro metafunctions.
|
|
|
|
We want to be able to introspect for the existence of inner elements to an enclosing type
|
|
without producing compiler errors. Of course if we absolutely know what types we have and
|
|
that a nested type exists, and these declarations are within our scope, we can always use
|
|
an expression like T::InnerType without error. But this is often not the case when doing template
|
|
programming since the type being passed to us at compile-time in a class or function template
|
|
is chosen at instantiation time.
|
|
|
|
One solution to this is afforded by the library itself. Given an enclosing type 'T'
|
|
which we know must exist, either because it is a top-level type we know about or
|
|
it is passed to us in some template as a 'class T' or 'typename T', and given an inner type
|
|
named 'InnerType' whose existence we would like ascertain, we can use a TTI_HAS_TYPE(InnerType) macro and it's related
|
|
tti::has_type_InnerType metafunction to determine if the nested type 'InnerType' exists. This solution is perfectly valid
|
|
and, with Boost MPL's selection metafunctions, we can do compile-time selection to generate the
|
|
correct template code.
|
|
|
|
However this does not scale that well syntactically if we need to drill down further from a
|
|
top-level enclosing type to a deeply nested type, or even to look for some deeply nested type's
|
|
inner elements. We are going to be generating a great deal of boost::mpl::if_ and/or
|
|
boost::mpl::eval_if type selection statements to get to some final condition where we know we
|
|
can generate the compile-time code which we want.
|
|
|
|
[heading The solution]
|
|
|
|
The TTI library offers a better solution in the form of constructs which work with
|
|
nested types without producing a compiler error if the nested type does not exist, but still
|
|
are able to do the introspecting for inner elements that our TTI macro metafunctions do.
|
|
|
|
We have already seen one of those constructs, the macro TTI\_MEMBER\_TYPE,
|
|
which generates a metafunction based on the name
|
|
of an inner type. But instead of telling us whether that inner type exists it instead returns
|
|
a typedef 'type' which is that inner type if it exists, else it is an unspecified type if it
|
|
does not. In this way we have created a metafunction, very similar in functionality to
|
|
boost::mpl::identity, but which still returns some unspecified marker 'type' if our nested type is invalid.
|
|
|
|
We can use the functionality of TTI\_MEMBER\_TYPE to construct nested types
|
|
for our other macro metafunctions, without having to use the T::InnerType syntax and produce a compiler
|
|
error if no such type actually exists within our scope. We can even do this in deeply nested contexts
|
|
by stringing together, so to speak, a series of these macro metafunction results.
|
|
|
|
As an example, given a type T, let us create a metafunction where there is a nested type FindType
|
|
whose enclosing type is eventually T, as represented by the following structure:
|
|
|
|
struct T
|
|
{
|
|
struct AType
|
|
{
|
|
struct BType
|
|
{
|
|
struct CType
|
|
{
|
|
struct FindType
|
|
{
|
|
};
|
|
}
|
|
};
|
|
};
|
|
};
|
|
|
|
In our TTI code we first create a series of member type macros for each of our nested
|
|
types:
|
|
|
|
TTI_MEMBER_TYPE(FindType)
|
|
TTI_MEMBER_TYPE(AType)
|
|
TTI_MEMBER_TYPE(BType)
|
|
TTI_MEMBER_TYPE(CType)
|
|
|
|
Next we can create a typedef to reflect a nested type called FindType which has the relationship
|
|
as specified above by instantiating our macro metafunctions.
|
|
|
|
typedef typename
|
|
tti::member_type_FindType
|
|
<
|
|
typename tti::member_type_CType
|
|
<
|
|
typename tti::member_type_BType
|
|
<
|
|
typename tti::member_type_AType
|
|
<
|
|
T
|
|
>::type
|
|
>::type
|
|
>::type
|
|
>::type MyFindType;
|
|
|
|
We can use the above typedef to pass the type as FindType
|
|
to one of our macro metafunctions. FindType may not actually exist but we will not generate
|
|
a compiler error when we use it.
|
|
|
|
As one example, let's ask whether FindType has a static member data called MyData of type 'int'. We add:
|
|
|
|
TTI_HAS_STATIC_MEMBER(MyData)
|
|
|
|
Next we create our metafunction:
|
|
|
|
tti::has_static_member_MyData
|
|
<
|
|
MyFindType,
|
|
int
|
|
>
|
|
|
|
and use this in our metaprogramming code. Our metafunction now tells us whether the nested type
|
|
FindType has a static member data called MyData of type 'int', even if FindType does not actually
|
|
exist as we have specified it as a type.
|
|
|
|
We can also directly find out whether the deeply nested type 'FindType'
|
|
actually exists in a similar manner. Our metafunction would be:
|
|
|
|
TTI_HAS_TYPE(FindType)
|
|
|
|
tti::has_type_FindType
|
|
<
|
|
typename
|
|
tti::member_type_CType
|
|
<
|
|
typename
|
|
tti::member_type_BType
|
|
<
|
|
typename
|
|
tti::member_type_AType
|
|
<
|
|
T
|
|
>::type
|
|
>::type
|
|
>::type
|
|
>
|
|
|
|
Because this duplicates much of our code for the 'MyFindType' typedef to
|
|
create our nested type, we can instead, and much more easily, pass our type 'MyFindType', since we already
|
|
have it in the form of a type,
|
|
to another metafunction called 'tti::valid_member_type', which returns a boolean constant
|
|
which is 'true' if our nested exists or 'false' if it does not.
|
|
|
|
Using this functionality with our 'MyFindType' type above
|
|
we could create the nullary metafunction:
|
|
|
|
tti::valid_member_type
|
|
<
|
|
MyFindType
|
|
>
|
|
|
|
directly instead of replicating the same functionality with our 'tti::has_type_FindType' metafunction.
|
|
|
|
The using of TTI\_MEMBER\_TYPE to create a nested type which may or may not exist, and which can
|
|
subsequently be used with our macro metafunctions whenever a nested type is required, without producing
|
|
a compiler error when the type does not actually exist, is the main reason we have separate
|
|
but similar functionality among our macro metafunctions to determine whether a member data, a member function, or a static member
|
|
function exists within an enclosing type.
|
|
|
|
In the more general case, when using TTI\_HAS\_MEMBER and TTI\_HAS\_STATIC\_MEMBER, the signature for the member
|
|
data, member function, and the function portion of a static member function is a composite type. This makes
|
|
for a syntactical notation which is easy to specify, but because of that composite type notation we
|
|
can not use the nested type functionality in TTI\_MEMBER\_TYPE very easily. But
|
|
when we use the TTI\_HAS\_MEMBER\_DATA, TTI\_HAS\_MEMBER\_FUNCTION, and TTI\_HAS\_STATIC\_MEMBER\_FUNCTION
|
|
the composite types in our signatures are broken down into their individual types so that using
|
|
TTI\_MEMBER\_TYPE, if necessary, for one of the individual types is easy.
|
|
|
|
[heading A more elegant solution]
|
|
|
|
Although using TTI\_MEMBER\_TYPE represents a good solution to creating a nested type
|
|
without the possible compile-time error of the T::InnerType syntax, reaching in to
|
|
specify all those ::type expressions, along with their repeated 'typename',
|
|
does get syntactically tedious.
|
|
|
|
Because of this the TTI library offers a parallel set of
|
|
metafunctions to the macro metafunctions where the 'types' specified are themselves nullary metafunctions.
|
|
This parallel set of metafunctions, using nullary metafunctions to specify individual types,
|
|
rather than the actual types themselves,
|
|
are called 'nullary type metafunctions'. In this group there is also a nullary metafunction
|
|
paralleling our TTI\_MEMBER\_TYPE macro metafunction, and therefore a further construct
|
|
making the specifying of nested types easy and error-free to use.
|
|
|
|
This group of nullary type metafunctions will be fully explained later.
|
|
|
|
[endsect]
|