Concrete curiously recurring template pattern
Using the "curiously recurring template pattern" it's possible to
achieve something like virtual functions with compile-time
bindings. However, I ran into some problems with this when I wanted to
implement the equivalent of a concrete class with overridable
functions. That is, a class that can be used itself, but also allows
for derivation and function overrides. I think the example below is
more or less a typical example of the CRTP:
#include <iostream>
#include <ostream>
template<class T>
struct algo_base
{
static void use_algo()
{
T::impl_one();
T::impl_two();
}
static void impl_one()
{
std::cout << "algo_base::impl_one\n";
}
static void impl_two()
{
std::cout << "algo_base::impl_two\n";
}
};
So what I'm talking about is that it's not possible to use the
algo_base class directly, without introducing a derived class. You can
do something like this:
struct algo_leaf : public algo_base<algo_leaf>
{
};
but this isn't open to further inheritance - the hierarchy stops as
soon as you instantiate the algo_base instance. Effectively, only leaf
nodes in the class hierarchy can be concrete, and they don't allow
overriding.
Some people probably think this is a good thing, from an OO point of
view. I might even agree, but just for the sake of argument, let me
present one option I came up with. The basic problem with the
algo_base above, is that there is no default template argument, so you
can't use algo_base<>. Using the boost::mpl library, we can fix this
as follows:
#include <iostream>
#include <ostream>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_same.hpp>
struct no_override { };
template<typename Base, typename Override>
struct maybe_override
{
typedef typename boost::mpl::if_
<boost::is_same<Override, no_override>,
Base,
Override>::type type;
};
template<typename Ovr = no_override>
struct algo_base
{
typedef algo_base<Ovr> self_type;
typedef typename maybe_override<self_type, Ovr>::type most_derived;
static void use_algo()
{
most_derived::impl_one();
most_derived::impl_two();
}
static void impl_one()
{
std::cout << "algo_base::impl_one\n";
}
static void impl_two()
{
std::cout << "algo_base::impl_two\n";
}
};
So this looks reasonably promising. At least it's now possible to use
algo_base<> as a concrete class. The default template argument of
no_override means that most_derived ends up being algo_base<>, and the
use_algo function would correctly call the base-class versions.
Implementing an intermediate class that allows derivation is only
slightly trickier:
template<typename Ovr = no_override>
struct intermediate :
algo_base<typename maybe_override<intermediate<Ovr>, Ovr>::type>
{
typedef intermediate<Ovr> self_type;
typedef typename maybe_override<self_type, Ovr>::type most_derived;
static void impl_one()
{
std::cout << "intermediate::impl_one\n";
}
};
struct derived : intermediate<derived>
{
static void impl_two()
{
std::cout << "derived::impl_two\n";
}
};
int main()
{
algo_base<>::use_algo();
derived::use_algo();
return 0;
}
This outputs:
algo_base::impl_one
algo_base::impl_two
intermediate::impl_one
derived::impl_two
However, this seems quite difficult for what it's actually doing - all
the mpl stuff obscures the real intention. Does anyone have
suggestions for simplifying this, or maybe an entirely different
approach?
--
Raoul Gough.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]