cppannotations/yo/advancedtemplates/typetype.yo
2008-08-05 14:07:07 +00:00

104 lines
4.1 KiB
Text

Although em(class) templates may be partially specialized, em(function)
templates may not. At times that can be annoying. Assume a function template
is available implementing a certain unary operator that could be used with the
tt(transform) (cf. section ref(TRANSFORM)) generic algorithm:
verb(
template <typename Return, typename Argument>
Return chop(Argument const &arg)
{
return Return(arg);
}
)
Furthermore assume that if tt(Return) is tt(std::string) then the
specified implementation should not be used. Rather, with tt(std::string) a
second argument tt(1) should always be provided (e.g., if tt(Argument) is a
bf(C++) string, a tt(std::string) is returned holding a copy of the function's
argument, except for the argument's first character, which is chopped off).
Since tt(chop()) is a function, it is not possible to use a
partial specialization. So it is not possible to specialize for
tt(std::string) as attempted in the following erroneous implementation:
verb(
template <typename Argument>
std::string chop<std::string, Argument>(Argument const &arg)
{
return string(arg, 1);
}
)
it em(is) possible to use overloading, though. Instead of using partial
specializations em(overloaded function templates) could be designed:
verb(
template <typename Return, typename Argument>
Return chop(Argument const &arg, Argument )
{
return Return(arg);
}
template <typename Argument>
std::string chop(Argument const &arg, std::string )
{
return string(arg, 1);
}
)
This way it em(is) possible to distinguish the two cases, but at the
expense of a more complex function call (e.g., maybe requiring the use of the
ti(bind2nd()) binder (cf. section ref(FUNADAPT))
to bind the second argument to a fixed value) as well as the need to
provide a (possibly expensive to construct) dummy argument to allow the
compiler to choose among the two overloaded function templates.
Alternatively, overloaded versions em(could) use the tt(IntType) template
(cf. section ref(INTTYPE)) to select the proper overloaded version. E.g.,
tt(IntType<0>) could be defined as the type of the second argument of the
first overloaded tt(chop()) function, and tt(IntType<1>) could be used for the
second overloaded function. From the point of view of program efficiency this
is an attractive option, as the provided tt(IntType) objects are extremely
lightweight: they contain no data at all. But there's also an obvious
disadvantage: there is no intuitively clear association between on the
one hand the tt(int) value used and on the other hand the intended type.
In situations like these it is more attractive to use another lightweight
solution. Instead of using an arbitrary int-to-type association, an
intuitively clear and automatic type-to-type association is used. The
tt(struct TypeType) is a lightweight type wrapper, much like tt(IntType) is a
lightweight wrapper around an tt(int). Here is its definition:
verb(
template <typename T>
struct TypeType
{
typedef T Type;
};
)
This too is a lightweight type as it doesn't have any data fields
either. tt(TypeType) allows us to use a natural type association for
tt(chop())'s second argument. E.g, the overloaded functions can now be defined
as follows:
verb(
template <typename Return, typename Argument>
Return chop(Argument const &arg, TypeType<Argument> )
{
return Return(arg);
}
template <typename Argument>
std::string chop(Argument const &arg, TypeType<std::string> )
{
return std::string(arg, 1);
}
)
Using the above implementations any type can be specified for
tt(Result). If it happens to be a tt(std::string) the correct overloaded
version is automatically selected. E.g.,
verb(
template <typename Result>
Result chopper(char const *txt)
{
return chop(std::string(txt), TypeType<Result>());
}
)
Using tt(chopper()), the following statement will produce the text
`tt(ello world)':
verb(
cout << chopper<string>("hello world") << endl;
)