Alf P. Steinbach wrote:
So what's "OtherClass" used for, then?
One solution may be to split the single template definition into a
general one and a specialization.
However, with the vague specification (e.g. "OtherClass") it's
difficult to give an example that wouldn't probably be misleading.
Indeed, I guess I over-simplified the example. My intent was to
extract only the syntactic problem I was running into, but I lost
the global design on the way. So I'll try to be more precise...
I currently have a template class (Example in my previous post)
which acts as a specialized container of objects (T, SomeClass,
OtherClass, ...), using a std::map as its underlying implementation.
For simplicity, I'll pretend that it is a std::map<unsigned,int>, and
get rid of the useless template parameters (policies).
It's member methods almost always return either a reference to itself,
or a modified copy. Most of these methods are symmetrical.
So it could be written:
template< /*original arguments ommitted*/ >
class Base
{
public:
Base();
Base(const Base&);
Base& operator =(const Base&);
Base& foo(int i)
{
// do whatever with i and map_
return *this;
};
Base bar(int i) const
{
Base tmp(*this);
tmp.foo(i);
return tmp;
};
private:
std::map<unsigned,intmap_;
};
FYI foo/bar is actually about 20 pairs of symmetric overriden
functions (FinalType& fn1() / FinalType fn1() const, ...).
Due to the number of template arguments (6), this template class is
_always_ used through a typedef (which in reality often spans over 6-
10 lines, so without typedef the code would be unreadable):
typedef Base< /*...*/ Derived1;
typedef Base< /*...*/ Derived2;
etc...
So Derived1::bar returns a Derived1 (aka. Base</*...*/due to the
typedef), while Derived1::foo returns a Derived1&.
But I now feel the need to add an optional rule enforcer to some of
the derived classes (let's say to Derived2) so I thought I'd do the
following :
template</*...*/>
class Base
{
public:
Base(); // rules_ is initialized to NULL
Base(const Base&);
Base& operator =(const Base&);
~Base();
Base& foo(int i)
{
// do whatever with i and map_
if (rules_)
rules_->Enforce_foo(i,map_);
return *this;
};
Base bar(int i) const
{
Base tmp(*this);
tmp.foo(i);
return tmp;
};
protected:
struct RulesInterface
{
virtual void Enforce_foo(int, std::map<unsigned,int>&) const = 0;
};
Base(const RulesInterface* rules)
// Initialize just like Base(), plus,
rules_(rules)
{
};
private:
std::map<unsigned,intmap_;
const RulesInterface* rules_;
};
so I still can write
typedef Base</*...*/Derived1;
but also
class Derived2 : public Base</*...*/>
{
public:
Derived2() : Base</*...*/>(&myRules_), myRules_() {};
Derived2(const Derived2&);
Derived2& operator =(const Derived2&);
private:
struct MyRules : public Base</*...*/>::RulesInterface
{
virtual void Enforce_foo(int i, std::map<unsigned,int>& m) const
{
// do whatever with i and m
};
};
const MyRules myRules_;
};
A few things about the above code :
1) even though Derived2 passes a pointer to myRules_ while it isn't
yet constructed, it's (more or less) ok since Base doesn't refer to it
in it's ctors nor in it's dtor.
2) any Base derivative is "final" by design (it would make no sense to
derive again from DerivedN).
However the problem with that new version of Base/Derived2 is that the
existing codebase expects Derived2 to return Derived2 objects (or
Derived2& depending on which method is called).
Here I have two choices:
a) either override Base's methods in Derived2 so that they return
Derived2 instead of Base, which means code duplication = The Root Of
All Evil IMO, even worse than what's lying below...
b) or, tweak Base so that it can return a Derived2 object if needed
(which was the reason of my original post).
So now I have my Base template looking like this:
class SelfType{};
template</*... ,*/ class F = SelfType>
class Base
{
template<class U>
struct helper { typedef U Type; };
template<>
struct helper<SelfType{ typedef Base</*...*/Type; };
typedef typename helper<F>::Type FinalType;
public:
Base(); // rules_ is initialized to NULL
Base(const Base&);
Base& operator =(const Base&);
~Base();
FinalType& foo(int i)
{
// do whatever with i and map_
if (rules_)
rules_->Enforce_foo(i,map_);
return static_cast<FinalType&>(*this);
};
FinalType bar(int i) const
{
FinalType tmp(*this);
tmp.foo(i);
return tmp;
};
protected:
struct RulesInterface
{
virtual void Enforce_foo(int, std::map<unsigned,int>&) const = 0;
};
Base(const RulesInterface* rules)
// Initialize just like Base(), plus,
rules_(rules)
{
};
private:
std::map<unsigned,intmap_;
const RulesInterface* rules_;
};
The advantage of this solution is that it doesn't break any old code
(namely, typedef's with F defaulted to SelfType), while still allowing
me to write Derived2 just like I wrote it above, without additional
junk.
The disadvantage is that IMHO this implementation is plain ugly, so we
come to the very reason of my previous post: "do you think there is a
better design?"
I hope the simplified code I just posted isn't too messy, but there
might be a few typos left from the simplification of my original code.
--
IR