One does not need to know all possible template instantiations in
adwance but pays by a circular dependency (all classes in the hierarchy
depend on the double-dispatcher which in turn depends on all classes in
the hierarchy). It is probably possible to eliminate the dependency
a-la Acyclic Visitor, but I didn't try to go this far.
Is somebody interested? I can post it here. Unfortunately, I cannot
host the files (volunteers are welcome).
--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
I would be very interested in your solution. In addition to posting, you may
want to put the doc on one of those free Websites. Thanks.
Andrei
Here goes.
After further analysis I came to conclusion that
my "template virtual functions" are nothing but
straightforward application of the Visitor pattern.
As many of you already know, Visitor is not just for
double dispatch. Visitor is a method of extending a
hierarchy with new "virtual" functions.
Here's a toy hierarchy that accepts visitors.
class Citizen;
class Employee;
class Parent;
class PersonV/*isitor*/ {
public:
void Visit (Citizen*) = 0;
void Visit (Employee*) = 0;
void Visit (Parent*) = 0;
};
class Person {
public:
virtual void Accept (PersonV&) = 0;
};
class Citizen : public Person {
void Accept (PersonV& pv) {
pv.Visit (this);
}
public:
int passport_no();
};
class Employee : public Person {
void Accept (PersonV& pv) {
pv.Visit (this);
}
public:
int employee_id();
};
class Parent : public Person {
void Accept (PersonV& pv) {
pv.Visit (this);
}
public:
int n_of_kids();
};
Here's how we add functionality to the hierarchy:
class PersonPrinter : public PersonV {
private:
std::ostream& os;
public:
PersonPrinter (std::ostream& os_) : os(os_) {}
void Visit (Citizen* s) {
os << "Citizen # " << s->passport_no();
}
void Visit (Employee* e) {
os << "Employee # " << s->employee_id();
}
void Visit (Parent* p) {
os << "Parent with " << s->n_of_kids() << "kids";
}
};
void print_person (Person* p, ostream& os) {
PersonPrinter pp(os);
p.Accept(pp);
}
In effect, we've extended the hierarchy with a polymorphic
"print_person" function, _as if_ print_person were a virtual
function in Person hierarchy. The syntax is different
(we don't get to write 'p->print(os)' but 'print_person(p, os)'
instead), but the net effect is the same. Isn't Visitor wonderful?
Let's consider the PersonPrinter again. Suppose that now we want to
print to wide character streams too. And tomorrow we'll surely
want to print to std::basic_stream<mycompany::toolkit::unicode_t>...
what, you say std::basic_stream is not guaranteed to support
mycompany::toolkit::unicode_t? Then we'll want to print to
mycompany::toolkit::advanced_stream<mycompany::toolkit::unicode_t>,
as soon as mycompany::toolkit gang finishes their advanced_stream.
No problemo, boss.
template <class Os>
class GenericPersonPrinter : public PersonV {
private:
Os& os;
public:
GenericPersonPrinter (Os& os_) : os(os_) {}
// the rest is copied verbatim from PersonPrinter
};
template <class Os>
void generic_print_person (Person* p, Os& os) {
GenericPersonPrinter<Os> pp(os);
p.Accept(pp);
}
Now what've we done? We've extended the hierarchy with a
polymorphic function, exactly as before -- only now it's
a <generic> function. Q.E.D.
--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]