Tag Dispatching and Inheritance
In the previous
blog
we described Tag Dispatching by Type.
Though very convenient, when many classes (tagged by as many
tags) share the same implementation, we get as many dispatch structs
with
the same implementation (or, better, forwarding to the same struct).
This
can be avoided by using similarities between classes, or between tags.
In
Boost.Geometry, for example, some geometries are
single while others
are
multi.
That multi-ness can be recognized by the metafunction
is_multi which
results in
true
for a multi-polygon and in
false
for a single polygon. We can include an IsMulti boolean template
parameter in the dispatch structure, so that it is selected not only on
tag dispatching, but also in
is_multi.
This similarity can also be implemented using a
tag hierarchy and
inheritance.
Let's go back to the fruit example. All citrus fruit species have pulp
vesicles. There
are many
citrus
fruits species. And, of course, we
don't want to repeat
the dispatch structure
has_vesicles
so many times. So we try to define our tag hierarchy:
struct citrus_tag {};
struct pome_tag {};
struct apple_tag : pome_tag {};
struct pear_tag : pome_tag {};
struct banana_tag {};
struct orange_tag : citrus_tag {};
struct lemon_tag : citrus_tag {};
struct lime_tag : citrus_tag {};
And then we couple the tags, like we did in previous blog:
template <typename T> struct tag {};
template <> struct tag<apple> { typedef apple_tag type; };
template <> struct tag<pear> { typedef pear_tag type; };
template <> struct tag<orange> { typedef orange_tag type; };
template <> struct tag<lime> { typedef lime_tag type; };
template <> struct tag<banana> { typedef banana_tag type; };
The new thing, besides the inheritance in the tags above, is that we
now define a
tag_cast
metafunction:
template
<
typename Tag, typename BaseTag,
typename BT2 = void, typename BT3 = void, typename BT4 = void,
typename BT5 = void, typename BT6 = void, typename BT7 = void
>
struct tag_cast
{
typedef typename boost::mpl::if_
<
typename boost::is_base_of<BaseTag, Tag>::type,
BaseTag,
// Try next one in line:
typename tag_cast<Tag, BT2, BT3, BT4, BT5, BT6, BT7, void>::type
>::type type;
};
template <typename Tag>
struct tag_cast<Tag, void, void, void, void, void, void, void>
{
// If not found, take specified tag, so do not cast
typedef Tag type;
};
We can now check if a tag
is
a citrus tag, by calling defining a new tag as
tag_cast<tag, citrus_tag>::type. This new tag is either a
citrus tag, or it is the tag it already was.
And we can dispatch by this tag as well. So it looks like:
namespace dispatch
{
template <typename T> struct has_vesicles : boost::false_type {};
template <> struct has_vesicles<citrus_tag> : boost::true_type {};
}
template <typename Fruit>
std::string has_vesicles(Fruit const& fruit)
{
// (Potentially) go up in hierachy: take tag corresponding to Fruit,
// downcast to citrus_tag if possible
typedef typename tag_cast<typename tag<Fruit>::type, citrus_tag>::type tag;
return std::string("has vesicles: ")
+ (dispatch::has_vesicles<tag>::value ? "true" : "false");
}
This tag_cast, as proposed above, can not only cast to one base-class,
but also to a range
of base-classes. The definition
typedef typename tag_cast<typename tag<Fruit>::type, citrus_tag, pome_tag>::type tag;
will result in a citrus_tag if it is a citrus, a pome_tag if it is a
pome, and otherwise it results in the input tag. Besides this, it also
walks through a tag
hierarchy, so if citrus_tag and pome_tag were both derived from, e.g.,
a
rosid_tag,
and rosid_tag was specified in this call, it would result in a
rosid_tag.
The sample program
here
shows two base classes, not listed here.
Our main program now displays correctly if the specific fruits have
vesicles or not
int main()
{
using namespace fruit;
apple a("my apple");
pear p("my pear");
orange o("my orange");
std::cout << has_vesicles(a) << std::endl;
std::cout << has_vesicles(p) << std::endl;
std::cout << has_vesicles(o) << std::endl;
return 0;
}
Back to Boost.Geometry. Until now it is not done, but we might
depricate
the is_multi meta-function and replace it by this:
struct multi_tag;
struct multi_point_tag : multi_tag {};
struct multi_linestring_tag : multi_tag {};
struct multi_polygon_tag : multi_tag {};
And instead of calling is_multi, we call tag_cast<tag,
multi_tag>. We then know if it is a multi and can dispatch on
it. The same for linear. This will probably be very convenient, it has
to be worked out a little more.
Casting and (multiple)
inheritance
The tag_cast as implemented above casts to
specified
tags. We can not just call the parent-tag. One of the reasons for this
is multiple inheritance.
Tag inheritance can be multiple. This is convenient because, of course,
there can be different tag classifications for the same set of tags.
For example: if a geometry
contains segments, or is linear, or is areal. So we might get:
struct multi_tag;
struct multi_point_tag : multi_tag {};
struct multi_linestring_tag : multi_tag, linear_tag {};
struct multi_polygon_tag : multi_tag, areal_tag {};
When calling tag_cast, the results depends on the order of the
specified base tags. And so does the dispatching.