I've been thinking about Scott Meyers' discussion of multiple dispatch
in _More Effective C++_ item 31. (Using the example of a video game
with spaceships, asteroids and space stations moving around, Scott
explores the possibilities of different ways to process collisions
between different objects.) What Scott eventually comes up with is a
map of pointers to collision-processing functions, keyed by <string,
string> pairs. Unfortunately, as Scott says on page 248, "everything
we've done will work fine as long as we never need to allow
inheritance-based type conversions when calling collision-processing
functions". The problem is that Scott's design uses the type names of
the GameObjects, thusly:
void processCollision( GameObject& object1,
GameObject& object2 )
{
HitFunctionPtr phf = lookup( typeid(object1).name(),
typeid(object2).name() );
if (phf) phf( object1, object2 )
else throw UnknownCollision( object1, object2 );
}
HitFunctionPtr lookup( const string& class1,
const string& class2 )
{
static auto_ptr<HitMap>
collisionMap( initializeCollisionMap() );
HitMap::iterator mapEntry =
collisionMap->find( make_pair(class1, class2 ));
if ( mapEntry == collisionMap->end() ) return 0;
return (*mapEntry).second;
}
HitMap * initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)[ make_pair("Spaceship", "Asteroid") ]
= &shipAsteriod;
...
}
In other words, this design works fine for Spaceship, SpaceStation and
Asteroid objects. On the other hand, suppose you introduce
MilitarySpaceship and CivilianSpaceship classes inheriting from
Spaceship, which should have the same collision behavior. Unless you
explicitly add entries to the collisionMap for the Spaceship subclasses
(can you say "maintenance nightmare?"), the lookup() function will throw
an UnknownCollision exception whenever presented with a
MilitarySpaceship.
(If you're still confused, go reread item 31 -- Scott tells this so much
better than I can. If you don't have a copy of _More Effective C++_, go
out and buy one; you can use the tips and I'm sure Scott can use the
royalties.)
The weakness here is the use of attributes of the typeid object to
generate the collisionMap keys. The problem is that we need to get the
same identifier both *with* a GameObject (in the lookup() function) and
*without* a GameObject (in the initializeCollisionMap() function). The
obvious trick would be to add a static virtual function getKey() to the
classes derived from GameObject -- but you can't have a static virtual
function. Then I thought of having two functions, one static and one
virtual, both using the same internal data, like so:
class GameObject
{
public:
virtual const string& getKey() const = 0;
...
}
class Spaceship: public GameObject
{
public:
virtual const string& getKey() const
{ return myName; };
static const string& getKeyStatic()
{ return myName; };
private:
static const string myName("Spaceship");
...
}
class Asteroid: public GameObject
{
public:
virtual const string& getKey() const
{ return myName; };
static const string& getKeyStatic()
{ return myName; };
private:
static const string myName("Asteroid");
...
}
class MilitarySpaceship: public Spaceship
{
public:
// I want MilitarySpaceships to act like Spaceships,
// so I don't provide a getKey();
// instead, I inherit Spaceship::getKey()
...
}
class CivilianSpaceship: public Spaceship
{
public:
// ditto for CivilianSpaceships
...
}
HitMap * initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)[ make_pair(Spaceship::getKeyStatic(),
Asteroid::getKeyStatic() ) ]
= &shipAsteroid;
...
}
void processCollision( GameObject& object1,
GameObject& object2 )
{
HitFunctionPtr phf = lookup( object1.getKey(),
object2.getKey() );
if (phf) phf( object1, object2 )
else throw UnknownCollision( object1, object2 );
}
Now I'm using a virtual function for the calls to lookup() and a static
function for the calls to initializeCollisionMap(), and as a bonus I've
also gotten rid of those irritating non-portable magic strings in
initializeCollisionMap(). (I hates magic strings, I does :-) Not only
that, but now my MilitarySpaceships and CivilianSpaceships both use
Spaceship collision logic. Problem solved, right?
Well, at least until I decide to add a Torpedo class (deriving from
MilitarySpaceship) which needs its own collision-processing logic.
(Torpedoes, of course, go "boom!" when they hit things.) I have to
write something like this:
class Torpedo: public MilitarySpaceship
{
public:
virtual const string& getKey() const
{ return myName; };
static const string& getKeyStatic() // see below
{ return myName; };
private:
static const string myName("Torpedo");
...
}
But I remember that nonvirtual functions are supposed to represent
invariance over specialization, and thus redefinining getKeyStatic() in
subclasses of Spaceship is a no-no. Rats.
But I'm stubborn, and I come up with another idea -- move the statics
out of the GameObject hierarchy and into their own class, thusly:
class GameObjectKeys
{
// This could go into the CollisionMap class
// that Scott mentions on page 249
public:
static const string& Spaceship("Spaceship");
static const string& Asteroid("Asteroid");
static const string& SpaceStation("SpaceStation");
// Note that we're not going to define anything
// for MilitarySpaceship or CivilianSpaceship
// since we want them to behave like Spaceships
// But I do want to define something for Torpedo
static const string& Torpedo("Torpedo");
}
class GameObject
{
// unchanged
public:
virtual const string& getKey() const = 0;
...
}
class Spaceship: public GameObject
{
public:
virtual const string& getKey() const
{ return GameObjectKeys::Spaceship; };
...
}
class MilitarySpaceship: public Spaceship
{
public:
// unchanged
...
}
class CivilianSpaceship: public Spaceship
{
public:
// likewise unchanged
...
}
class Torpedo: public MilitarySpaceship
{
public:
virtual const string& getKey() const
{ return GameObjectKeys::Torpedo; };
...
}
class Asteroid: public GameObject
{
public:
virtual const string& getKey() const
{ return GameObjectKeys::Asteroid; };
...
}
HitMap * initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)[ make_pair(GameObjectKeys::Spaceship,
GameObjectKeys::Asteroid ) ]
= &shipAsteriod;
(*phm)[ make_pair(GameObjectKeys::Torpedo,
GameObjectKeys::Asteroid ) ]
= &torpedoAsteroid;
...
}
*Now* I think I've got it. My MilitarySpaceships collide just like
CivilianSpaceships, while my Torpedos have their own handling logic.
initializeCollisionMap() gets its key values from the same place the
GameObjects do. I don't have any extra magic strings laying around.
I'm not doing nasty things with redefining non-virtual functions. Of
course, I'm not all that happy about the coupling between GameObjectKeys
and the GameObjects hierarchy, but sometimes you've gotta do what you've
gotta do.
So my questions to the C++ guruship at large are:
1) Will this work? Or am I missing something that would be blindingly
obvious were I only more knowledgeable?
2) Why _can't_ you have a virtual static function? There must be a good
reason, but I have no clue as to what that good reason is.
--
Edmund Schweppe aka merl...@my-deja.com
Blissfully free of official positions
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! ]
Where does the std forbid redefining static functions in derived
classes? I've been doing it for years. If there is a requirement like
that, it's unreasonable and a defect report shall be filed. If
this "nonvirtual functions are supposed to represent invariance over
specialization" is yet another "rule of thumb". You just found an
exception.
> So my questions to the C++ guruship at large are:
> 1) Will this work? Or am I missing something that would be blindingly
> obvious were I only more knowledgeable?
Yes it works. I've been doing this for years. MEC++ item 31 is really
not state of the art. I've personally thought up at least 2 C++ idioms
to implement multiple dispatch better (more extendible, more flexible
and more efficient) than item 31. I do thank Scott for the wonderful
book, which I got the first edition (in 1996) with Scott's signature.
It really gave me a good start to think about the more interesting
aspects of C++.
> 2) Why _can't_ you have a virtual static function? There must be a
good
> reason, but I have no clue as to what that good reason is.
because "virtual static function" is an oxymoron. virtual is
wrt "this" - the object, you don't need an object to call a static
function. BTW, I do understand what YOUR "virtual static function"
mean, just write:
virtual const string& getKey() const {
static string key("Whatever");
return key;
}
and get rid of the dummy class.
A.
[ snippage el grande ]
I've gotten a couple of email responses to my question
> 2) Why _can't_ you have a virtual static function? There must be a
> good reason, but I have no clue as to what that good reason is.
which tell me that perhaps I didn't really ask the question I wanted to.
(Thanks to Drew Davis, Douglas Kirkpatrick and Perry Rapp for your
answers to the original question.) Let me try again.
I know that when I invoke a virtual function on an *object* through a
pointer-to-base or reference-to-base, the function call is dynamically
dispatched through the vtbl. Thus, the compiler knows enough at compile
time to set up the appropriate vtbl, and the runtime environment knows
where to look for the vtbl when necessary. Thus, given the code below:
// incomplete classes
class B
{
public:
virtual void foo()
{ cout << "B.foo()" << endl; }
}
class D1: public B
{
public:
virtual void foo()
{ cout << "D1.foo()" << endl; }
}
class D2: public B
{
public:
// no redefinition of foo()
}
class D2A: public D2
{
public:
virtual void foo()
{ cout << "D2A.foo()" << endl; }
}
creating an object bar of type D2 and invoking bar.foo() actually ends
up calling B::foo() with *this pointing to bar. (Specifically,
bar.foo() translates roughly to (bar.vtbl[foo_offset])(&bar) where
foo_offset is whatever the appropriate offset is for foo in bar's vtbl.)
Obviously, for this mechanism to work, the compiler must be able to
generate code to populate the vtbl, recognizing that D2A::foo() should
not be dispatched to B::foo() while D2::foo() should.
What I meant by "virtual static function" was a virtual function (e.g.,
one that can legally be redefined through the C++ inheritance
mechanisms) that is also static (e.g., one that does *not* need access
to *this and thus can be called as a class method). Thus, I'd like to be
able to write something like:
class B
{
public:
static virtual void bletch()
{ cout << "B::bletch()" << endl; }
}
class D1: public B
{
public:
static virtual void bletch()
{ cout << "D1::bletch()" << endl; }
}
class D2: public B
{
public:
// no redefinition of bletch() either
}
class D2A: public D2
{
public:
static virtual void bletch()
{ cout << "D2A::bletch()" << endl; }
}
and then be able to write mainline code thusly:
void main()
{
B::bletch(); // should print "B::bletch()"
D1::bletch(); // should print "D1::bletch()"
D2::bletch(); // should print "B::bletch()"
D2A::bletch(); // should print "D2A::bletch()"
}
It seems to me that there's enough information available to the compiler
to be able to tell that D2 doesn't override B::bletch() while D1 and D2A
do, even without a *this pointer.
My question then is -- I know I can't do this, but why?
I'd be interested to hear about these. I guess they're on topic, so you may
want to post them.
> > 2) Why _can't_ you have a virtual static function? There must be a
> good
> > reason, but I have no clue as to what that good reason is.
>
> because "virtual static function" is an oxymoron.
The simple reason that something is an oxymoron is not reason enough for
that thing not to exist. For instance: "virtual constructor", "dynamic
overloading", or... "millitary intelligence" :o).
"Dynamic overloading" happens to be tightly linked with the discussion we
are having here. That's a short way for me to say "Making functions virtual
with respect to more than one object". (Does anyone agree?)
Basically the ideal C++ engine that does what Item 31does is:
* you provide a collection of overloaded functions, like:
void Collide(Spaceship&, Spaceship&);
void Collide(Spaceship&, Asteroid&);
void Collide(Asteroid&, Asteroid&);
...
* you call the engine passing it the name of the function and two references
to GameObject:
GameObject& obj1 = ...;
GameObject& obj2 = ...;
Item31Engine(Collide, obj1, obj2);
The magic function Item31Engine will figure out the dynamic types of obj1
and obj2, and will call the appropriate overload, as if you did the casts
yourself.
The code above is not valid C++. I said "the ideal C++ engine". However, one
can get surprisingly close to it.
Andrei
> > > 2) Why _can't_ you have a virtual static function? There must be a
> > good
> > > reason, but I have no clue as to what that good reason is.
because the exact type of an object is known only by the object itself.
So to do the correct dispatch you have to inspect the object, which isn't
possible in a static method since there is no object to inspect.
> "Dynamic overloading" happens to be tightly linked with the discussion we
> are having here. That's a short way for me to say "Making functions virtual
> with respect to more than one object". (Does anyone agree?)
>
> Basically the ideal C++ engine that does what Item 31does is:
>
> * you provide a collection of overloaded functions, like:
>
> void Collide(Spaceship&, Spaceship&);
> void Collide(Spaceship&, Asteroid&);
> void Collide(Asteroid&, Asteroid&);
> ...
>
> * you call the engine passing it the name of the function and two references
> to GameObject:
>
> GameObject& obj1 = ...;
> GameObject& obj2 = ...;
> Item31Engine(Collide, obj1, obj2);
>
> The magic function Item31Engine will figure out the dynamic types of obj1
> and obj2, and will call the appropriate overload, as if you did the casts
> yourself.
>
> The code above is not valid C++. I said "the ideal C++ engine". However, one
> can get surprisingly close to it.
I wonder why the keyword 'Visitor' hasn't been mentioned yet. Whenever certain
conditions are matched (at least one of both class hierarchies is stable and relatively
small) it is the ideal way in C++ to do binary dispatch based on runtype polymorphism.
Stefan
_______________________________________________________
Stefan Seefeld
Departement de Physique
Universite de Montreal
email: seef...@magellan.umontreal.ca
_______________________________________________________
...ich hab' noch einen Koffer in Berlin...
> [ snippage el grande ]
Good examples are to be followed...
> I've gotten a couple of email responses to my question
> > 2) Why _can't_ you have a virtual static function? There must be a
> > good reason, but I have no clue as to what that good reason is.
> which tell me that perhaps I didn't really ask the question I wanted to.
Indeed it must - when determining the layout of D2::__vtbl
> What I meant by "virtual static function" was a virtual function (e.g.,
> one that can legally be redefined through the C++ inheritance
> mechanisms) that is also static (e.g., one that does *not* need access
> to *this and thus can be called as a class method). Thus, I'd like to be
> able to write something like:
Aha - you think you can only override virtual functions. This isn't
true, e.g.
class Bnv {
void foo() { cout << "B's non-virtual foo\n"; }
}
class Dnv {
void foo() { cout << "D's non-virtual foo\n"; }
}
is perfecly legal, and with
Bnv b; Dnv d;
b.foo();
d.foo();
The result is:
B's non-virtual foo
D's non-virtual foo
The surprise is in
Bnv *base;
base=&b; base->foo();
base=&d; base->foo()
which prints.
B's non-virtual foo
B's non-virtual foo
Because *base is a Bnv.
> class B
> {
> public:
> static virtual void bletch()
> { cout << "B::bletch()" << endl; }
> }
> class D1: public B
> {
> public:
> static virtual void bletch()
> { cout << "D1::bletch()" << endl; }
> }
> class D2: public B
> {
> public:
> // no redefinition of bletch() either
> }
> class D2A: public D2
> {
> public:
> static virtual void bletch()
> { cout << "D2A::bletch()" << endl; }
> }
> and then be able to write mainline code thusly:
> void main()
> {
> B::bletch(); // should print "B::bletch()"
> D1::bletch(); // should print "D1::bletch()"
> D2::bletch(); // should print "B::bletch()"
> D2A::bletch(); // should print "D2A::bletch()"
> }
> It seems to me that there's enough information available to the compiler
> to be able to tell that D2 doesn't override B::bletch() while D1 and D2A
> do, even without a *this pointer.
Yes -and if you leave off the virtual, the above program is just fine, and
will work as expected. Virtual is only needed if you call a function on
an object of a derived class via a pointer to the base class part.
You can't use pointers to a base class to call static member functions.
HTH,
Michiel Salters
Oh, my. You are confused :-) This will work exactly the way you
want if you declare your functions static. There's no virtualness
needed.
Hmmm... looks like you haven't read the "More Effective C++" yet.
the "vistor" pattern is actually the first scheme mentioned in item 31.
It's simple and efficient. But it suffers from the dependence of vistor
interface upon concrete elements. i.e., everytime you add a new
element, the vistor interface needs to be modified. This is
unacceptible if you are providing an extendible framework/library for
vistors and elements -- interacting polymorphic objects. This can be
easily solved by implement a vtbl like dispatcher in the vistor
interface (the book provided a simple minded implementation which is
neither very extendible nor efficient.)
The map of non-member functions solution is very extendible and mostly
ideal for symmetric multiple dispatching, but it suffers from
inefficiency (every call involves costly key construction and map
lookup) which may or may not be an issue depending up the type of
application. Also using non-member functions to deal with interaction
between objects usually implies friends galore or over publicity.
A.
me too, robert martin posted a sample of triple dispatch a while ago that
was similar to this kind of thing. I hacked it up for a java presentation
using monsters (which could have a deep inhertance tree), but its similar
to c++:
// visitor with triple dispatch. from a post to comp.object by robert
martin http://www.oma.com
/*
In this case, we are actually using a triple dispatch, because we have
two
types to resolve. The first dispatch is the virtual Attacks function
which
resolves the type of the object upon which Attacks is called. The second
dispatch is the virtual Accept function which resolves the type of the
object passed into Attacks. Now that we know the type of both objects,
we
can call the appropriate global function to calculate the Attack. This
is done by the third and final dispatch to the Visit function.
*/
interface Monster
{
Monster attacks(final Monster monster);
void accept(MonsterVisitor visitor);
}
class Orc implements Monster
{
public Monster attacks(final Monster monster)
{
OrcVisitor visitor=new OrcVisitor(this);
monster.accept(visitor);
return visitor.monster();
}
public void accept(MonsterVisitor visitor)
{ visitor.visit(this); } // visit Orc
}
class Dragon implements Monster
{
public Monster attacks(final Monster monster)
{
DragonVisitor visitor=new DragonVisitor(this);
monster.accept(visitor);
return visitor.monster();
}
public void accept(MonsterVisitor visitor)
{ visitor.visit(this); } // visit Dragon
}
// visitors.
interface MonsterVisitor
{
abstract public void visit(Orc orc);
abstract public void visit(Dragon dragon);
}
class DragonVisitor implements MonsterVisitor
{
DragonVisitor(final Dragon dragon)
{ this.dragon=dragon; }
public void visit(Orc orc)
{ monster=Interaction.attacks(dragon,orc); }
public void visit(Dragon dragon)
{ monster=Interaction.attacks(this.dragon,dragon); }
Monster monster() {return monster; }
private Monster monster;
private final Dragon dragon;
}
class OrcVisitor implements MonsterVisitor
{
OrcVisitor(final Orc orc)
{ this.orc=orc; }
public void visit(Orc orc)
{ monster=Interaction.attacks(this.orc,orc); }
public void visit(Dragon dragon)
{ monster=Interaction.attacks(orc,dragon); }
Monster monster() {return monster; }
private Monster monster;
private final Orc orc;
}
// interactions
class Interaction
{
static Monster attacks(final Dragon d,final Dragon d2)
{ System.out.print(d+" vs "+d2); return d; }
static Monster attacks(final Dragon d,final Orc o)
{ System.out.print(d+" vs "+o); return d; }
static Monster attacks(final Orc o,final Dragon d)
{ System.out.print(o+" vs "+d); return d; }
static Monster attacks(final Orc o,final Orc o2)
{ System.out.print(o+" vs "+o2); return o; }
}
public class MartinsVisitor
{
public static void main (String[] args)
{
Monster monster1=new Orc(); System.out.println(monster1);
Monster monster2=new Orc(); System.out.println(monster2);
Monster monster3=new Dragon();
System.out.println(monster3);
System.out.println(", "+monster1.attacks(monster2)+"
survived");
System.out.println(", "+monster1.attacks(monster3)+"
survived");
System.out.println(", "+monster3.attacks(monster1)+"
survived");
for(;;);
}
}
hth
--
Ray (will hack java for food) http://home.pacbell.net/rtayek/
hate Spam? http://www.blighty.com/products/spade/
And you haven't read John Vlissides's Pattern Hatching column in
the C++ Report. ;) In the book form, look at the sections on
Visitor Caveats and Visitor Revisited. He also has yet another
Visitor-dispatch solution in this month's C++ Report that's worth
reading. It feels like a compromise between a Visitor and a map/tree.
And for general double-dispatch implementation hints, look at your
local CLOS implementation's code. IIRC, some free implementations
for Scheme use a caching, optimized map (STklos and guile's GOOPS).
They're worth examining if you need a fully general solution.
Jason
get rid of the virtual keyword, add necessary semicolons at the end of
class definitions, change void main to int main, you've got a legal
code that behaves exactly the way you wanted. What's the problem?
Looks like you're confusing "overriding" and "hiding". ie., when
dealing with non-virtual functions, we don't call it "overriding". std
mandates that variables and/or non-virtual functions "hides" the vars
and/or non-virtual functions of the same name (name only) in base
classes. compiler may (or may not) issue a warning if overloaded
(virtual or not) functions are partially redefined in derived classes.
A.
You can have them in Smalltalk.
The Smalltalk approach is to reify classes at runtime. That is, for each
compile-time class there is a runtime object which represents it. The
static variables become instance variables of that object. Static
functions become ordinary functions, and the class object forms the "this"
pointer and stores the vtbl (or whatever) for virtual functions.
For this to be useful, different classes must have different instance
variables and so must be different types. Call the class of a class the
"metaclass"; we're saying different classes must have different
metaclasses. And then it all works. A static method of the base class can
be overridden by a static method of the derived class.
It is beautiful, simple and powerful. It's also fairly hard to get your
head around, to teach etc. It involves extra runtime overhead - eg we've
added at least 2 runtime objects to the system for every class. There may
be issues of compatibility with C, I don't know.
Stroustrup briefly mentions the idea in D&E, under "Meta-Objects"
$14.2.8.1. He adds type-checking issues to the list of problems.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
Right. With Robert Martin's Acyclic Visitor, things get even better.
Basically you can implement a pretty darn elegant dynamic overloading
engine if the hierarchy(ies) invloved is(are) visitable in the Acyclic
Visitor sense.
Andrei
<merl...@my-deja.com> schrieb in im Newsbeitrag:
80rvhs$4tk$1...@nnrp1.deja.com...
> 2) Why _can't_ you have a virtual static function? There must be a good
> reason, but I have no clue as to what that good reason is.
Contrary to what others have answered, I think the language could allow it.
There may be a rationale for not allowing it, but it escapes me.
The idea is to have per-class behaviour be dispatched on an per-object
basis.
Actually there is one case where we do have a virtual static function in the
language: operator delete
The strange thing about that one is only, that you state whether it should
be 'virtual' at the declaration of the destructor.
By analogy one could allow the following:
<EXAMPLE>
struct A
{
virtual static char* name() { return "A"; }
};
struct B
{
virtual static char* name() { return "B"; }
};
int main()
{
A a;
B b;
std::cout << A::name() << B::name() << std::endl;
std::cout << a.name() << b.name() << std::endl;
// now for polymorphic dispatch
A* poly;
poly = &a;
std::cout << poly->name();
poly = &b;
std::cout << poly->name() << std::endl;
}
</EXAMPLE>
Which should print:
AB
AB
AB
I had a need for this behaviour more than once and find the usual workaround
more error-prone:
struct A
{
static char* static_name() { return "A"; }
virtual char* name() { return static_name(); }
};
struct B : A
{
static char* static_name() { return "B"; } // might forget this - it
could be needed in a template
virtual char* name() { return static_name(); } // might forget this,
if not pure in A
};
This is also more difficult to read, as you have to think up another name
for the same behaviour. The requirement that static_name() and name() return
the same value for a most derived object cannot presently be expressed in
the language. The compiler could just generate code like the above (without
the unneeded implicit this argument).
The dispatch of this also resembles the way typeid is resolved:
if called for a type do static (compile-time) dispatch
if called for an object do dynamic (runtime) dispatch
It would even be possible to have a pure static virtual. As usual it would
have to be defined if called directly.
-- Jörg Barfurth
To comp.std.c++: should this be considered for the distant next version of
the standard ?
What do you think ?
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]
Then the word "static" has different semantics/connotation in
Smalltalk. Given the "virtual" and "static" definitions in C++, you
can't have "virtual static function" period -- unless you change the
semantics of virtual and static in this particular context just to
confuse people more. This is a simple logic assertion...
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
It sounds more like circular reasoning to me, and a pedantic attachment to
terminology. I interpreted the question as, "why do static class methods
have the semantics they do in C++?" "Because if they didn't they would be
different" is not a helpful answer. Of course they would be different. C++
would be a different language. The interest is in the benefits and
drawbacks such a difference would entail.
> This is a simple logic assertion...
That's why I posted. Many replies suggested that people were so set in the
current C++ model they were unable to think of any other way the language
could be. In my view, the original question was a good one. It's quite
reasonable to want methods which are attached to classes rather than class
instances but which can still be overridden in subclasses. It is not a
logical impossibility. To attack the terminology with which the question
was asked is to miss the point.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> Then the word "static" has different semantics/connotation in
> Smalltalk. Given the "virtual" and "static" definitions in C++, you
> can't have "virtual static function" period -- unless you change the
> semantics of virtual and static in this particular context just to
> confuse people more. This is a simple logic assertion...
"static" has (too) many meanings in C++ already. But:
- In the given context (class members), "static" means "per class" as
opposed to "per object".
- In that same context "virtual" means "can vary with the dynamic type of
the object".
But one feature that most certainly changes with the dynamic type of an
object is the class it belongs to ;-o
So to me the concepts seem to be compatible (they may not be fully
orthogonal though).
IMHO there is no real change in semantics necessary to combine those
concepts.
For elaboration see my recent post 'static virtual ?' in this thread.
-- Jörg Barfurth
But is overloading (in addition to hiding), not overriding. This is a
sublte, but important distinction.
>, and with
>
> Bnv b; Dnv d;
> b.foo();
> d.foo();
>
> The result is:
>
> B's non-virtual foo
> D's non-virtual foo
>
> The surprise is in
> Bnv *base;
> base=&b; base->foo();
> base=&d; base->foo()
>
> which prints.
>
> B's non-virtual foo
> B's non-virtual foo
>
> Because *base is a Bnv.
And because you overloaded the method instead of overriding it.
> Yes -and if you leave off the virtual, the above program is just
fine, and
> will work as expected. Virtual is only needed if you call a function
on
> an object of a derived class via a pointer to the base class part.
> You can't use pointers to a base class to call static member
functions.
All very true, but to do what he wants I *think* you need the ability
to call the "static" method through the object pointer... i.e. you
don't know the type to call the method. Another use for the typeof
keyword ;) *chuckles*. Sorry, I really don't want to open that can of
worms, at least not in this thread... if anyone wants to dwell on this
after I mentioned it, please, start a new thread.
I haven't read all your article but I will summarize my
position anyway: when faced with an apparent multiple
dispatch (MD) problem:
- check if MD is really wanted: to we really want to
parametrize an operation @ over A and B, or if there
are f, g and h such that:
h (f (A), g (B)) = A @ B
f resp. g is parametrized on only one type (A resp. B);
example:
stream_type << object_type
is really
put_chars_into_buffer (get_buffer(stream_type),
to_chars(object_type))
- check if MD has to occur at runtime or compile-time, with
template (and type-safe code...); in the lastest case, overloading
will solve the problem
- will nested switch/if handle it ?
- do we really want polymorphism on both arguments ?
To me MD is a complicated thing, and I think it's often made
of smaller units. Before parametrizing anything, I split MD
into these simplier pieces. It's difficult to reason about
MD in general because we don't know a-priori how to split into
smaller pieces an arbitrary MD problem.
--
Valentin Bonnard
the short answer is probably efficiency and static type safety.
Stefan
_______________________________________________________
Stefan Seefeld
Departement de Physique
Universite de Montreal
email: seef...@magellan.umontreal.ca
_______________________________________________________
...ich hab' noch einen Koffer in Berlin...
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I noticed both Scott Meyers, and Stroustrup (in C++PL $15.4.4.1) use the
name rather then type_info::before(). Why is this? What is the before()
method for if not for use in maps etc?
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ sample code sacrificed to appease the gods of bandwidth ]
> > My question then is -- I know I can't do this, but why?
> get rid of the virtual keyword, add necessary semicolons at the end of
> class definitions, change void main to int main, you've got a legal
> code that behaves exactly the way you wanted. What's the problem?
Because C++ static functions don't work the way I want when called
through a base class pointer. The original idea was to use the same
getKey() function to both initialize the collision map (when called
statically) and to look up collision-processing function pointers (when
called virtually through a GameObject pointer). However, there's no
mechanism in C++ to do what I want.
[ chalupa ... err, quoted material ... snipped ]
> > You can't use pointers to a base class to call static member
> functions.
> All very true, but to do what he wants I *think* you need the ability
> to call the "static" method through the object pointer... i.e. you
> don't know the type to call the method.
Exactly.
> Another use for the typeof keyword ;) *chuckles*.
Actually, no. When calling the method through the object pointer, I
*need* to use the virtual overriding mechanism. Ideally, I'd like some
way of doing the following (using the classes I defined in my original
post, where Spaceship inherits from GameObject, MilitarySpaceship
inherits from Spaceship, and Torpedo inherits from MilitarySpaceship):
// Static dispatch
Spaceship::getKey(); // returns "Spaceship"
Torpedo::getKey(); // returns "Torpedo"
// Dispatch via base class pointer
GameObject* p1 = new MilitarySpaceship();
GameObject* p2 = new Torpedo();
p1->getKey(); // returns "Spaceship"
p2->getKey(); // returns "Torpedo"
I'm slightly confused here, is the behaviour you are looking for equivalent
to a virtual function that delegates to the static? eg,
struct Base
{
static void MyStatic() { }
virtual void CallMyStatic() { MyStatic(); }
}
struct Derived
{
static void MyStatic() { } // hide Base::MyStatic() - or call it
something different
// override Base::CallMyStatic to use Derived::MyStatic
virtual void CallMyStatic() { MyStatic(); }
}
// Usage:
int main()
{
Base* pBase = new Base();
Base* pDerived = new Derived();
pBase->CallMyStatic(); // equivalent to Base::MyStatic();
pDerived->CallMyStatic(); // equivalent to Derived1::MyStatic();
}
The problem being that whenever you write a new static, you also have to
override the virtual.
Another way of doing this would be a map from type_info to function
pointers. Then you could do something like
CallMyStatic(typeid(MyPointer))();
This is probably conceptually closer to what you're looking for, but a bit
more cumbersome to implement, since you need some way of registering the
type_info of each candidate object. It does have the advantage that you
could use templates to let the compiler do normal overload resolution on the
static, eg
template <class T>
struct MyDispatcher
{
static void Dispatch() { T::MyStatic(); }
};
Then the mapping is typeid(class) --> MyDispacher<class> for each class that
is used.
Cheers,
Ian McCulloch
Ah, now I understand you meant: you want to use the same name for both
virtual and static function. spoiled by name overloading, don't you?
that's just a minor convenience, but it would another yet another rule
for compilers to check that your trivial "virtual static function"
doesn't use any members. You can always use:
static const string& staticKey();
virtual const string& getKey() const { return staticKey(); }
The above code is perfectly legal and portable -- that was my point.
A.
Scott acknowledged the comparison based upon type_info::name() to be a
bug. It's got to be on his errata pages.
Basically you cannot do anything of interest with type_info::name(),
other than outputting it to a debug console. I guess that even making
it part of error messages that are seen by the client is not very
adequate.
Andrei
Not quite; I want to have *one* function that is *both* virtual and
static. If I have two functions, then someday some nitwit maintenance
programmer will get one right and one wrong. If there's only one
function, the odds of an error are reduced; should an error occur, at
least the code will be consistent.
> You can always use:
> static const string& staticKey();
> virtual const string& getKey() const { return staticKey(); }
> The above code is perfectly legal and portable -- that was my point.
No question about being portable, and I agree that it's legal C++.
However, now that same nitwit maintenance programmer can call the static
method through a base pointer, which will result in legal but erroneous
code as follows:
struct GameObject
{
virtual const string& getKey() const = 0;
};
struct Spaceship: public GameObject
{
static const string& getStaticKey() { return "Spaceship"; }
virtual const string& getKey() const { return getStaticKey(); }
};
struct MilitarySpaceship: public Spaceship
{
// no override of getKey(), thus inherits Spaceship::getKey();
};
struct Torpedo: public MilitarySpaceship
{
static const string& getStaticKey() { return "Torpedo"; }
virtual const string& getKey() const { return getStaticKey(); }
};
int main()
{
Spaceship *ps = new Torpedo();
ps->getKey(); // invokes Torpedo::getKey()
ps->getStaticKey(); // invokes Spaceship::getStaticKey()
if ( ps->getKey() == ps->getStaticKey() )
// the programmer probably expects this
cout << "Same key" << endl;
else
// but the programmer is going to get this
cout << "Keys differ" << endl;
}
Hopefully, the nitwit maintenance programmer will only make this mistake
once. OTOH, I'd much prefer not to give him the opportunity to screw
up in the first place.
--
Edmund Schweppe aka merl...@my-deja.com
Blissfully free of official positions
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Have you considered using a Prototype Pattern instead? For example:
namespace GameObjectPrototypes {
static const SpaceShip spaceShip;
static const Asteroid asteroid;
static const Torpedo torpedo;
//...
}
void RegisterCollisionTypes() {
using namespace GameObjectPrototypes;
RegisterCollisionType( spaceShip, asteroid, &shipAsteroid );
RegisterCollisionType( spaceShip, torpedo, &shipTorpedo );
//...
}
void RegisterCollisionType( const GameObject &a, const GameObject &b,
CollisionFunc f ) {
collisionMap[ make_pair( a.GetKey(), b.GetKey() ) ] = f;
}
In other words, construct an entire spaceship and call its virtual
function. That way you don't need the static function at all. Arguably it
is simpler and less error prone to create an entire object that to fiddle
about trying to share strings between individual functions.
Admittedly the creation could be expensive, but you only need one object
of each type. The namespace is a reflection of your GameObjectKeys, but if
RegisterCollisionTypes is the only place the static keys are used you
could make the objects local variables of that function.
On the other hand, keeping the prototypes around might be useful for other
things. For example, you might want to check the size of asteroids by
comparing them with a prototypical bigAsteroid. Or create new objects by
cloning the existing, pristine ones - the prototypes would act as a
repository for various game object parameters.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Make the static method private.
Schobi
If the virtual one just forwards to the static version it is pretty hard
to see how an employable programmer could get it wrong.
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
One of the purposes of MD is to avoid using nested switches and ifs, which
are a maintenance nightmare.
Andrei
[snipped the rest of the explanation and code]
I understand this. Quite often, when the non-existant typeof keyword
is discussed it is unclear as to whether it would/should return the
static type or the runtime type of an object. The general consensus is
that it would be statically typed, as is sizeof, but if it were runtime
typed instead it would suffice for this particular problem. There are
definate reasons why runtime typing would be problematic to implement
at best, thus the wink and chuckle in my remark.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I'm not sure I see this. Leave the virtual keyword off and statics
would be EXACTLY as they are today. Put the virtual keyword on and the
static call would be as efficient and type safe as other virtual
methods are (typesafety wouldn't be sacrificied in any way, since the
type is deduced from either the reference/pointer being called through
same as a normal virtual method is, or would be deduced from the
explicit name given for "standard static method calls", and the call
would be "less efficient" by one level of indirection when called
through a pointer/reference). Under this scenario, the ONLY difference
between a static method and a non-static method is that static methods
would not be passed a this pointer. I realize C++ doesn't do this and
I can see why it wouldn't have been thought of to begin with, but I
don't see any reason the semantics couldn't be changed.
Having recently gone through that wonderful exercise known as Y2K
remediation, any illusions that I might have had of a minimal competency
level for "employable" programmers have been eternally shattered. (I'm
reminded of one MS-Windows application which had *three* calendar-based
date entry dialogs. The dialogs looked the same and had the same
functionality; however each dialog was individually coded and used a
different set of global variables to pass dates to and from its caller.)
If a compiler will let a programmer do something, sooner or later some
programmer will do it.
--
Edmund Schweppe aka merl...@my-deja.com
Blissfully free of official positions
Because in C++, "Virtual functions" means "look up the exact
address of the function at runtime, based on the dynamic type
of the object" and "Static functions" means "a function which
does not use an instance of an object." Since there is no
instance to get the dynamic type of, there is no virtual table
to use.
> You can have them in Smalltalk.
>
> The Smalltalk approach is to reify classes at runtime. That is,
> for each compile-time class there is a runtime object which
> represents it. The static variables become instance variables
> of that object. Static functions become ordinary functions, and
> the class object forms the "this" pointer and stores the vtbl
> (or whatever) for virtual functions.
Very clever.
> For this to be useful, different classes must have different
> instance variables and so must be different types. Call the
> class of a class the "metaclass"; we're saying different
> classes must have different metaclasses. And then it all
> works. A static method of the base class can be overridden
> by a static method of the derived class.
Excellent! Well, problem solved.
> It is beautiful, simple and powerful. It's also fairly hard to
> get your head around, to teach etc. It involves extra runtime
> overhead - eg we've added at least 2 runtime objects to the
> system for every class.
That shouldn't be too much of a drawback. Smalltalk users want
decent performance, but my understanding is that they're not too
concerned about the absolute theoretical maximum.
> There may be issues of compatibility with C, I don't know.
How strange. Why would Smalltalk users be worried about
compatibility with C?
> Stroustrup briefly mentions the idea in D&E, under "Meta-Objects"
> $14.2.8.1. He adds type-checking issues to the list of problems.
Well, anyway, since the solution is to write the program in
Smalltalk, you should probably continue this thread in a different
newsgroup. Is there a comp.std.smalltalk? I suggest going back to
the original message and re-posting it; I would have cross-posted
this message to both groups, but it probably doesn't have enough
context to be meaningful to readers of that group.
--
All...@my-deja.com is a "Spam Magnet," never read.
Please reply in newsgroups only, sorry.
In my opinion I think the reason is quite simple:
a static method is not passed "this", and the (run time) type of this is
used to select a virtual method.
Lorenzo
--
+-----------------------------------------------------+
| Lorenzo Bettini ICQ# lbetto, 16080134 |
| PhD student in Computer Science |
| Florence - Italy |
| Home Page : http://w3.newnet.it/bettini |
| http://infostud.dsi.unifi.it/~bettini |
| Mail Home : lorenzo...@penteres.it |
| Mail University : bet...@dsi.unifi.it |
| http://www.mokabyte.it Java on line journal |
| http://rap.dsi.unifi.it/xklaim XKlaim language |
+-----------------------------------------------------+
It is. From http://www.aristeia.com/BookErrata/mec++-errata.html:
(Hoping the cut and paste works well...)
"When setting up collision maps, it would be safer to avoid the use of
literal strings like "SpaceShip". Instead, the maps should be
initialized via calls to typeid. That way it would be harder to
accidently use the wrong string for a class name. Furthermore, the
standard makes it clear that type_info::name can return just about
anything, so there's no portable use for the results of that function.
The map-related typedefs for the non-member solution of pp. 244-248
should really be these:
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
typedef map<pair<const type_info*, const type_info*>,
HitFunctionPtr> HitMap;"
It should also be noted that even on implementations where the
type_info::name approach might work it would be less efficient because
of the string comparisons.
Hi,
What is the need of using XXXVisitor classes? As far as I consider
everything works well without it making triple dispatch double (C++
variant):
class Orc;
class Dragon;
class Monster
{
protected:
virtual void accept( Orc & ) = 0;
virtual void accept( Dragon & ) = 0;
public:
virtual void attacks( Monster & ) = 0;
};
class Orc : public Monster
{
protected:
virtual void accept( Orc & orc )
{
// orc vs orc
}
virtual void accept( Dragon & dragon )
{
// orc vs dragon
}
public:
virtual void attacks( Monster & monster )
{
monster.accept( *this );
}
};
class Dragon: public Monster
{
protected:
virtual void accept( Orc & orc )
{
// dragon vs orc
}
virtual void accept( Dragon & dragon )
{
// dragon vs dragon
}
public:
virtual void attacks( Monster & monster )
{
monster.accept( *this );
}
};
If the destructor just deletes allocated memory it is pretty hard to
see how an employable programmer could get it wrong *chuckles*.
Programmers are human. Forget this and you're in major trouble.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Now you can't call the static method with out an instance pointer, so
basically it's not static any more. I'll illustrate what's wanted here:
class A
{
public:
static virtual print() { cout << "I'm an A!" << endl; }
};
class B : public A
{
public:
static virtual print() { cout << "I'm a B!" << endl; }
};
int main()
{
A a;
B b;
A* c = &b;
a.print();
b.print();
c->print();
A::print();
B::print();
}
This should output:
I'm an A!
I'm a B!
I'm a B!
I'm an A!
I'm a B!
With out the "static virtual" this program is totally valid except for
the line "c->print();", which will output "I'm an A!" instead of the
wanted "I'm a B!". This is possible, it's just not what we have today.
: I'm not sure I see this. Leave the virtual keyword off and statics
: would be EXACTLY as they are today.
Slight detour...
As languages such as Dylan (which has multiple dispatch) and Eiffel show,
every function could, in theory, be "virtual" with no efficiency loss.
What is required is post-likage optimization -- a lack C++ inherited from C.
The extra indirection required for a v-table can be optimized out when you
find that it's happens to be constant for every invocation in this build.
C++ already has enough to know when to optimize away taking a "this" pointer
as a parameter. The problem is in informing a calling function in another
file not to pass one, that this optimization has occured.
-mi
--
Micha Berger (973) 916-0287 MMG"H for 23-Nov-99: Shelishi, Vayishlach
mi...@aishdas.org A"H
http://www.aishdas.org Pisachim 73a
For a mitzvah is a lamp, and the Torah its light.
Hmmm.... looking at the map<> typedef above, it seems to me that there is no
comparison function defined for the pair<> keys. Is there a standard
operator< or less_than comparator for pair<> objects? I don't see a custom
comparator in Scott's errata. The default under SGI STL is to compare the
"first" field, then the "second" field if necessary, which seems reasonable
but may not be part of the Standard. But even if it is, I think there's a
problem:
The address returned by &typeid(T) isn't guaranteed to be unique within a
program (_The C++ Programming Language_, 3rd Ed, p. 415). So comparing
pointers to see if two types are the same is not guaranteed to work, and the
map above really needs a custom comparator to be correct. Or am I missing
something?
John Panzer
What would you expect to be the output if you replace main() with:
void f (A* pa)
{
pa->print ();
}
?
Your idea works only as long as the dynamic type of A* is also known
statically - unless you replace the C++ compilation model by something
smarter. Do you advocate such an innovation, or would you be content if static
virtuals merely work in special cases such as the above?
Gerhard Menzl
Not exactly. 'virtual' means "look up the exact address of the function at
runtime, _if_ called through a pointer or a reference." This is a
requirement for the caller.
'static' means "the function does not use 'this'." This is a requirement for
the function body. There is no conflict here.
A hypothetical virtual static function, therefore, will use dynamic dispatch
when called through a pointer or reference, but will also be callable
directly using the T::f() notation.
Under the current C++ rules I'd simulate the latter with:
template<class T> void call_virtual_static(void (T::*pmf)())
{
(T().*pmf)();
}
replacing T::f(); with call_virtual_static(&T::f);
Another possibility might be
template<class T> void call_virtual_static(void (T::*pmf)())
{
static T t;
(t.*pmf)();
}
if the constructors/destructors are expensive.
Full code follows:
#include <iostream>
struct A
{
virtual void f() { std::cout << "A::f();\n"; }
};
struct B: public A
{
virtual void f() { std::cout << "B::f();\n"; }
};
template<class T> void call_virtual_static(void (T::*pmf)())
{
static T t;
(t.*pmf)();
}
B b;
A* pa = &b;
int main()
{
pa->f();
call_virtual_static(&A::f);
call_virtual_static(&B::f);
return 0;
}
--
Peter Dimov
Multi Media Ltd.
What is _the_ static function. The condition was that the virtual function
(for a given object) does the same thing as the static function for the same
type. This requirement may be necessary in some designs that mix templates
and polymorphism.
If a maintenance programmer changes an existing class, one can indeed hope
that he will not change the simple forwarding virtual (although it still
remains only a hope).
The trouble comes when adding a new derived class. Here the requirement
could be missed or one of the functions' names misspelled. It is easy to
only override one of the functions without keeping the other in sync.
--Jörg Barfurth
Your version hardwires the action. If all you have is "attacks", that's
fine, but if you have other kinds of interaction you will have to update
your hierarchy more often. In effect you're replaced the triple <Monster,
Monster, Interaction> with <Monster, Monster>.
Admittedly the original code adds "attacks" methods all over the place,
which seems to defeat the object. I think we could avoid that with another
visitor. Something like:
// Other (Java) code as before.
class AttackVisitor implements MonsterVisitor {
public static Monster attack( Monster attacker, Monster victim ) {
AttackVisitor visitor = new AttackVisitor( victim );
attacker.accept( visitor );
return visitor.getResult();
}
private Monster victim;
private Monster result;
public Monster AttackVisitor( Monster victim ) {
this.victim = victim;
}
public getResult() { return result; }
public void visit( Orc attacker ) {
OrcVisitor visitor=new OrcVisitor( attacker );
victim.accept(visitor);
result = visitor.monster();
}
public void visit( Dragon attacker ) {
DragonVisitor visitor=new DragonVisitor( attacker );
victim.accept(visitor);
result = visitor.monster();
}
}
We can now add new routines like attack() without touching the Monster
hierarchy. We need to write N+1 visitors instead. This is a win if the
Monsters need to be stable.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
It tends to breaks encapsulation.
Laguages that support multiple dispatch such as Dylan and CLOS have differnt
mechanism for encapsulation. They use the concept of modules. In these
languages,
records, classes or types (depending on the terminology used by the language)
are
not used as the unit of encapsulation as C++ does. In C++ a class encapsulates
all it's members and defines access rights to them. In Dylan and CLOS the
concepts of type and of encapsulation are orthogonal.
In general, multimethods as they are sometimes called are defined outside of any
class i..e like a function
(in fact this concepts comes from functionnal languages). This is normal since
they don't know which types they are going to be called on. Also to do anything
usefull on the parameters that are passed to the multimethod, it must have
access
to
the members of every parameter.
Greg
Lorenzo Bettini <bet...@gdn.dsi.unifi.it> schrieb in im Newsbeitrag:
383ADFD4...@gdn.dsi.unifi.it...
>
> "J.Barfurth" wrote:
> > > 2) Why _can't_ you have a virtual static function? There must be a
good
> > > reason, but I have no clue as to what that good reason is.
>
> In my opinion I think the reason is quite simple:
>
> a static method is not passed "this", and the (run time) type of this is
> used to select a virtual method.
>
> Lorenzo
Generally you have to select a virtual method first, based on the dynamic
type of the object for which the method is invoked, and only then pass it
'this' as an argument. The type of 'this' may be different from both a
pointer to the static type and the dynamic (most derived) type of the
object. All three pointers could in fact have different values when
static_cast'ed to void*. A 'static virtual' method would just be called
without passing the this pointer. This would even save any overhead
necessary to adjust the value needed for 'this'.
As I pointed out before, there are two cases where we already get this kind
of sychronicity: typeid and class-specific operator delete(well sort of ...
in the latter case).
As opposed to typeid, the object expression used to access a static member
is always evaluated anyway. So we needn't introduce any more special cases
into the language to make 'static virtual' work. Moreover, it wouldn't break
existing code and you won't have to pay it's costs if you don't use it.
With the common 'virtual table' implementation of polymorphic dispatch, each
'static virtual' would just introduce another entry in the virtual table.
Another interesting aspect would be whether taking the address of such a
function (referred to through an lvalue)would yield the address of the final
overrider:
struct A { virtual static A* foo(); }
struct B : A { virtual static B* foo(); void bar(); }// note the
covariant return types
A* p = B::foo();
p->foo(); // calls B::foo
A* (*fp)() = &p->foo; // new syntax necessary
fp(); // calls B::foo
// assert(fp == &B::foo); // incompatible types because of covariance
assert(&p->bar == &B::bar); // works without covariance
I guess it should not be too difficult to implement this: For covariant
return types there would be some kind of stub to adjust the return value.
The function pointer could be copied right from the virtual table, so it
would point to that stub. As the pointer types are then unrelated to the
derived one, a conforming program cannot detect this.
An alternative would be to allow a pointer-to-member function to refer to a
static member function, but IMHO the former is much better.
-- Jörg Barfurth
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
I disagree. There are uses of functions that take only one object and
(a) use a base class interface only (no extra dispatch needed),
(b) do specialized things to derived objects (some kind of extra dispatch
needed in C++)
and don't need special access to the members of their parameter.
This can be extrapolated to cases where
(c) the function takes more than one parameter and needs to take special
action for certain combinations of dynamic types of their parameters
(multiple dispatch).
The point is, that the objects in question, in their public interface,
support atomic operations (i.e. take care of keeping their invariants
intact).
OTOH there are useful (complex) operations that can be built upon such
primitives. The object should not need to account for all functionality that
can (potentially) be built this way.
<BTW> Nor should it need to know about all (otherwise unrelated) classes it
may be paired with in single dispatch situations. That is the argument in
favor of (acyclic) visitor-based implementations of multiple
dispatch.Implementing multiple dispatch behaviour without a generic
mechanism for multiple dispatch introduces unneccesary coupling.</BTW>
In case (b) the first impetus (in a statically typed language) might be to
simply add a polymorphic method to the class hierarchy to reduce it to case
(a). (Luckily this is more difficult in case (c)).
Skipping the question of cohesion, this would also break encapsulation. The
added member would have unrestricted access to class internals. As the
assumption was that it could be realized without such access, this is not
necessary. It should therefore be avoided.
Therefore encapsulation (as related to cohesion and coupling) is actually an
argument in favor of multiple dispatch dispatching to global[*] functions:
Objects take care of their own state. Functionality (or implicit
relationships) between multiple objects, that does not belong to any single
one of the classes involved, should be built from primitives that respect
encapsulation. Using (non-friend) functions, the compiler can enforce this.
[*] global in this context. Member functions of classes that act upon our
hierarchy from the outside would also qualify.
Correct.
> Is there a standard
> operator< or less_than comparator for pair<> objects?
I'm not sure about this, but the answer is irrelavent for Scott's code,
since the "default" would have the problems you point out below.
> I don't see a custom
> comparator in Scott's errata. The default under SGI STL is to
compare the
> "first" field, then the "second" field if necessary, which seems
reasonable
> but may not be part of the Standard. But even if it is, I think
there's a
> problem:
>
> The address returned by &typeid(T) isn't guaranteed to be unique
within a
> program (_The C++ Programming Language_, 3rd Ed, p. 415). So
comparing
> pointers to see if two types are the same is not guaranteed to work,
and the
> map above really needs a custom comparator to be correct. Or am I
missing
> something?
No, you aren't missing anything. Or rather, what you are missing is
yet another mistake (I think). Scott has told me that when he tested
his code he used a specialization of std::less (if this is mentioned in
the errata, I missed it). So, his code for map is correct, but you're
missing the fact that he specialized std::less to work in this
context. The mistake (I *think*) is that the standard prohibits
specializing std::less for any but user defined types, and
std::pair<type_info*, type_info*> uses only implementation defined
types. This topic is being discussed in another thread I started in
this group (titled, I think, "Discussion: type_info"). Instead of
polluting this thread, I'd suggest taking further talk on this subject
to the other thread.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
You can't tell what the output will be without knowing the type of
object that pa points at. This is no different than a virtual
function. If print were purely a static the compiler, upon seeing the
above code, would say "hmmm... pa points to an A instance so we'll call
A::print". Add our extension of static virtual and the compiler would
say instead "hmmm... pa points to an A instance and A defines print to
be virtual, so look up the method within the virtual table and call
print through the virtual function pointer". The *ONLY difference in
this model between a static and a non-static method is that no "this"
pointer is passed to static methods. The static method may still be
virtual and retain an entry in the v-table, thus allowing calls through
pointers to behave virtually even though no "this" pointer is passed.
This isn't complicated.
> Your idea works only as long as the dynamic type of A* is also known
> statically - unless you replace the C++ compilation model by something
> smarter.
No, static typing is not required, and in fact it's not possible in the
face of polymorphism, which is exactly what we're dealing with here.
As I pointed out above, enabling C++ to do this is trivial. It does
require changing the compilation model, as the addition of any key word
would do, but the foundation of the change is in line with existing
practice, i.e. it behaves little different from normal virtual
invocation. I fail to see why some people continue to want to argue
that this is impossible.
> Do you advocate such an innovation, or would you be content if static
> virtuals merely work in special cases such as the above?
No, I would not be satisfied if it only worked in special cases. Like
all such practices it must be generic to be useful. There is *NO*
reason it must be useable only on special cases, however. A very
simple extrapolation of the current model would suffice admirably.
Right. Geez, this month has been brutal for MEC++ in this newsgroup.
Sigh.
Here's how I currently plan to word things in the MEC++ errata. I haven't
changed the on-line version yet, because I batch updates to the errata
lists. From an administrative point of view, this is easier for me, and it
also makes it possible for me to make periodic announcements about the
updates to my mailing list (see
http://www.aristeia.com/MailingList/index.htmlfor details):
When setting up collision maps, it would be safer to avoid
the use of literal strings like "SpaceShip". Instead, the
maps should be initialized via calls to typeid. That way it
would be harder to accidently use the wrong string for a
class name. Furthermore, the standard makes it clear
that type_info::name can return just about anything, so
there's no portable use for the results of that function.
That suggests that the map-related typedefs for the
non-member solution of pp. 244-248 should really be these:
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
typedef map<pair<const type_info*, const type_info*>,
HitFunctionPtr> HitMap;
That's not quite right, however, because the map's
ordering function would be
less<pair<const type_info*, const type_info*> >,
and there's no guarantee that that will (1) exist and
(2) behave in a reasonable fashion. Furthermore,
you're not allowed to define your own version of this
class, because it doesn't use any user-defined types.
You'd therefore have to write a custom comparison class
for the map:
class TICompare { ... }; // "type_info compare"
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
typedef map<pair<const type_info*, const type_info*>,
HitFunctionPtr, TICompare> HitMap;
Details are left to the reader :-)
With any luck, this is finally correct and standard-conforming. If not, I
know you'll let me know about it...
Scott
--
Scott Meyers, Ph.D. sme...@aristeia.com
Software Development Consultant http://www.aristeia.com/
Visit http://meyerscd.awl.com/ to demo the Effective C++ CD
Thanks. I encourage you to write up your approaches to DD for publication,
either as a magazine article or a web page. That way I could at least add
a link to your material from the "Interesting Comments" part of the MEC++
errata. Since publication of MEC++, I've received a lot of new information
on DD, and I have the outline of an article, "Double Dispatching
Revisited", here that I will probably never have time to write up. (I'd
post the notes, but they're a complete mess. I know, because I was going
to send them to somebody, and I finally decided they were too jumbled to be
meaningful to anybody but me.) Fortunately, I know somebody else (a
frequent contributor to this newsgroup) who is working on a new solution to
the DD problem, so perhaps there will be a new publication in this area in
the not-too-distant future. That would be nice.
FWIW, I'm not wild about designs that require that programmers provide
functions that allow classes to identify themselves, because it's too easy
to forget to add the necessary functions (static, virtual, or typically
both). That's why I prefer type_info-based solutions: the compiler can
provide the necessary information. For example, you could use each class's
type_info object as a class identifier, though you have to be careful,
because implementations are allowed to generate multiple type_info objects
for a given class. (All the objects are required to compare equal,
however.)
DD is an interesting topic. I'm still on the lookout for a solution that
requires minimal recompilation when hierarchies change (naturally,
"minimal" means "none", and Visitor fails this test), that's next to
foolproof to implement for classes involved in the DD (the need to write
class-identification functions would violate this goal), that supports
inheritance (unlike the final design in Item 31), and that localizes
changes to a single function when new classes are added to the DD hierarchy
or hierarchies (for my design, this was the hitmap initialization
function). I suspect it is possible to develop such a design, I just
haven't seen it. Yet.
Exactly.
By the same reasoning, C++ could also allow virtual static member
variables.
class Base
{
public:
virtual static int counter;
...
};
class Der: public Base
{
public:
virtual static int counter;
...
};
void foo(Base *o)
{
o->counter++; // depends on dynamic type of o
}
void bar()
{
Base b;
Der d;
foo(&b); // increments Base::counter
foo(&d); // increments Der::counter
}
-- David R. Tribble, da...@tribble.com, http://david.tribble.com --
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
Shucks. Perhaps you can find the time while preparing MEC++, 2nd
edition? (hint, hint, wheedle)
> FWIW, I'm not wild about designs that require that programmers provide
> functions that allow classes to identify themselves, because it's too
> easy to forget to add the necessary functions (static, virtual, or
> typically both). That's why I prefer type_info-based solutions: the
> compiler can provide the necessary information.
As the person who opened this can of worms in the first place, I can
safely say that "light dawns over Marblehead." It wasn't clear to me
from reading Item 31 that this was a design goal; thus explaining why my
proposals were all based on requiring subclasses of GameObject to
provide a getKey() method.
> DD is an interesting topic. I'm still on the lookout for a solution
> that requires minimal recompilation when hierarchies change
> (naturally, "minimal" means "none", and Visitor fails this test),
> that's next to foolproof to implement for classes involved in the DD
> (the need to write class-identification functions would violate this
> goal), that supports inheritance (unlike the final design in Item 31),
> and that localizes changes to a single function when new classes are
> added to the DD hierarchy or hierarchies (for my design, this was the
> hitmap initialization function). I suspect it is possible to develop
> such a design, I just haven't seen it. Yet.
Wow, you don't ask for much, do you? :-)
The requirement to support inheritance without class-identification
functions seems to be the hardest. *With* class-ID functions,
inheritance is trivial (just make them virtual and override when
necessary). *Without* them, however, somebody else has to come up with
a way to map subclasses which inherit parental implementations to those
parents. type_info can't do it on its own.
I suppose you could write something along the lines of the following
(based on the final MEC++ Item 31 design, page 245):
const string keyFromTypeInfo( const type_info& ti )
{
// given typeid information, return a string that can be used
// as part of the key for the collision map
// As stated in an earlier message, I tend to be *really* anal
// about "magic strings", thus:
static const string SpaceshipKey("Spaceship");
static const string TorpedoKey("Torpedo");
static const string SpacestationKey("SpaceStation"):
static const string AsteroidKey("Asteroid");
static const string NoSuchKey("DamnIdiotMaintenanceProgrammers");
// MilitarySpaceships and CivilianSpaceships
// collide just like regular Spaceships
if ( ti == typeid(SpaceShip) ||
ti == typeid(MilitarySpaceship) ||
ti == typeid(CivilianSpaceship) )
{
return SpaceshipKey;
}
// Torpedoes may be MilitarySpaceships,
// but they need their own collision behavior
else if ( ti == typeid(Torpedo) )
{
return TorpedoKey;
// Then, of course, we have our SpaceStations ...
else if ( ti == typeid(SpaceStation) )
{
return SpacestationKey;
}
// ... and our Asteroids
else if ( ti == typeid(Asteroid) )
{
return AsteroidKey;
}
// We should only get here in our worst nightmares
// (e.g., incompetent maintenance programmers wrecking
// our wonderful code)
else
{
assert(false);
return NoSuchKey;
}
}
HitMap * initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)[ make_pair(keyFromTypeInfo(typeid(SpaceShip)),
keyFromTypeInfo(typeid(SpaceShip))) ]
= &shipShip;
(*phm)[ make_pair(keyFromTypeInfo(typeid(SpaceShip)),
keyFromTypeInfo(typeid(Asteroid))) ]
= &shipAsteroid;
// etc., etc.
return phm;
}
// lookup is unchanged, but quoted here for reference
HitFunctionPtr lookup(const string& class1, const string& class2)
{
static auto_ptr<HitMap>
collisionMap(initializeCollisionMap());
HitMap::iterator mapEntry=
collisionMap->find(make_pair(class1, class2));
if (mapEntry == collisionMap->end) return 0;
return (*mapEntry).second;
}
void processCollision( GameObject& object1, GameObject& object2 )
{
HitFunctionPtr phf = lookup(keyFromTypeInfo(typeid(object1)),
keyFromTypeInfo(typeid(object2)));
if (phf) phf(object1, object2);
else throw UnknownCollision(object1, object2);
}
This design has two obvious drawbacks. For one, we have a prime example
of the hideous cascading if-then-else. For another, the "inheritance
mechanism" is going to be clear as mud to the nitwit maintenance
programmers of the universe, one of whom will (of course) be tasked to
maintain keyFromTypeInfo().
However, there's only one of these hideous cascades (unlike the first
Item 31 RTTI design, where there was one per GameObject subclass), and
we use the same logic to get keys during initialization and lookup.
Adding new GameObjects can be done without recompiling the existing
ones, and the new classes don't need to do anything special to support
DD. Inheritance is supported, albeit in an ugly manner.
Unfortunately, adding new classes may require changing *two* functions;
keyFromTypeInfo() always, and initializeCollisionMap() whenever new
collision-processing functions are added. So this supports *most* of
Scott's design goals. Oh, well. Three out of four ain't bad.
(By the by, I'd like to take this opportunity to express *my* thanks to
you, Scott, for a pair of great C++ books.)
--
Edmund Schweppe aka merl...@my-deja.com
Blissfully free of official positions
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I am very surprised that nobody mentionned a Robert C Martin paper
"Acyclic visitor". It seems to me that this technic solves all dependency
problems.
Unfortunatly, I dont have a web reference to this paper, just the E-mail of
the author
rma...@oma.com.
I didnt use this technic in real apps, but implemented it in little cases.
I found it very
flexible.
- No cyclic dependencies
- Support for catch-all functions
- Add new classes and new commands without any change in existing code.
(just add member functions in command class)
Try it:
This file is the acyclic visitor implementation
----- Command.h ------------
class BaseCommand {
public:
virtual ~Command(){}
template< class C> bool operator()( C& c) { return c.accept( *this);}
};
template< class C> class Command
: public virtual BaseCommand
{ public: virtual void apply( C&) = 0;}
#define VISITOR_ACCEPT( cl) \
virtual bool accept( BaseCommand& bc)\
{ if( Command< cl>* c = dynamic_cast< Command< cl>*>( &bc) != 0)\
{ c->apply( *this); return true;}\
else return false;}
#define VISITOR_ACCEPT_OR_FORWARD( cl, mother) \
virtual bool accept( BaseCommand& bc)\
{ if( Command< cl>* c = dynamic_cast< Command< cl>*>( &bc) != 0)\
{ c->apply( *this); return true;}\
else return mother::accept( bc);}
----- end Command.h ------------
How to use it:
In top level class (ie Shape) add a line
VISITOR_ACCEPT( Shape)
In child classes, add the line
VISITOR_ACCEPT_OR_FORWARD( Square, Shape)
To define a tool :
class GetNameTool
: public Command< Shape>,
public Command< Square>,
public Command< Ellipse> // to support a new shape
you have to add this
{
void apply( Shape&) { name_ = "unknown shape";}
void apply( Square&) { name_ = "square";}
void apply( Ellipse&) { name_ = "ellipse";} // and
this
string name() const { return name_.c_str();}
private:
string name_;
};
To use it :
void print_name( Shape& s)
{
GetNameTool get_name;
bool handled = get_name( s);
if handled cerr << get_name.name << endl;
else cerr << "Should not happen" << endl;
}
Dependancies are from tools to shapes but not from shapes to tools, even not
from
base shape to tool.
- Adding new shape involves adding tool support for this shape only
if it needs specific support. In the case above, if you add a new shape and
no support
for this shape in GetNameTool, apply( Shape&) will be called.
- Adding new tools involves no change in shapes.
Performance does not depend on the number of shapes. Penalty is 1 dynamic
cast.
IMHO, there is a large class of problems where this cost is acceptable.
Note: the above is not code sample. But I implemented some code using this
technic. It ran under HPUX aCC. For usable code, you will have to add
support for constness.
Hope it helps
Xavier Tarrago
I've mentioned my implementation here before; I don't know if you've
seen it. It places no requirements on the types or functions involved
other than normal polymorphism. It requires explicit "registration" of
all of the types and functions involved; this would normally be isolated
in an initialisation function, which is the only code that needs to be
changed when new classes or functions are added.
ftp://animal.ihug.co.nz/pub/multiple_dispatch.tgz
--
Ross Smith <ros...@ihug.co.nz> The Internet Group, Auckland, New Zealand
========================================================================
"Klingons do not make software releases. Our software *escapes*, leaving
a bloody trail of designers and quality assurance people in its wake."
I would add "in terms of type_info::before()" to the first sentence.
Otherwise it's just so tempting to compare the pointers, which is unsafe.
(since the type_infos won't be part of the same object, and may not be
unique).
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Thanks for the encouragement. But I really don't have the time (or just
too lazy) to do that. However, since it's really not rocket science
that requires pages to describe, I'll just post them here.
[snip]
> DD is an interesting topic. I'm still on the lookout for a solution
that
> requires minimal recompilation when hierarchies change (naturally,
> "minimal" means "none", and Visitor fails this test), that's next to
> foolproof to implement for classes involved in the DD (the need to
write
> class-identification functions would violate this goal), that supports
> inheritance (unlike the final design in Item 31), and that localizes
> changes to a single function when new classes are added to the DD
hierarchy
> or hierarchies (for my design, this was the hitmap initialization
> function). I suspect it is possible to develop such a design, I just
> haven't seen it. Yet.
One of my idioms is very similar to Robert Martin's "Acyclic Vistor" (I
learned the later afterwards in 97) which seems to satisfy all your
requirements (based upon my interpretation):
http://www.objectmentor.com/publications/acv.pdf
which he claims cannot be done without MI and RTTI, which is, of
course, nonsense.
The use of dynamic_cast can be a performance problem if you have a
vistor to handle a lot of trivial elements (useful to implement an
extendible state machine; requires MI from a long list of specific
vistor interfaces), since dynamic_cast is typically implemented with a
linear search in the hierarchy. In any case it's still probably more
efficient (in most cases) than any map based implementations.
In solving the above performance problem, I came up with a solution
that basically implments a vtable like virtual dispatch interface in
the vistor interface, which just involves a (probably inlined)
array/vector lookup for pointer to member function:
typedef DispatchTable<Element, Vistor> HandlerTable;
class Vistor {
public:
// failsafe
virtual int visitGeneric(Element&);
// inline dispatcher
int visit(int id, Element& x) {
if (dispatcher_.handles(id)) return dispatcher(id, x);
return visitGeneric(x);
}
void addHandlers(const HandlerTable& table) {
dispatcher_.add(table);
}
private:
Dispatcher<Element, Vistor> dispatcher_;
};
class Element {
public:
virtual int accept(Vistor&) = 0;
protected:
static int assignTypeId() { static int id; return ++id; }
};
template <class E, class V>
int
setElementHandler(HandlerTable& table, int (V::*f)(E&)) {
static DispatchableMemFun<Element, Vistor, E, V> handler;
handler.set(E::typeId(), f);
return table.add(handler);
}
class Element1 : public Element {
public:
virtual int accept(Vistor& v) { return v.visit(typeId(), *this); }
static int typeId() { static int id = assignTypeId(); return id; }
};
class Vistor1: public Vistor {
public:
Vistor1();
// can be overrided
virtual int visitElement1(Element1&);
virtual int visitElement2(Element2&);
...
virtual int visitElementn(Elementn&);
}
static HandlerTable v1table;
static int initV1table {
setElementHandler(v1table, &Vistor1::visitElement1);
setElementHandler(v1table, &Vistor1::visitElement2);
...
return setElementHandler(v1table, &Vistor1::visitElementn);
}
static int v1init = initV1table();
Vistor1::Vistor1() { addHandlers(v1table); }
Given the above interface and usage, it should be fairly easy to figure
out the template implementation of Dispatcher, Dispatchable,
DispatchableMemFun and DispatchTable (they are put in a library and
reusable in many contexts,) where you can include all sorts of static
type safe checks to insure the foolproofness of the registration. I've
put new elements and vistors in shared libraries (DLLs) and load them
dynamically as they needed.
Note, this solution uses neither MI nor RTTI.
Enjoy,
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> Otherwise it's just so tempting to compare the pointers, which is
unsafe.
> (since the type_infos won't be part of the same object,
If you rely on std::less to compare the pointers, they don't have to be
part of the same object.
> and may not be > unique).
Here I agree. I'm not sure whether 'unsafe' would be the right word.
Anyway, it might not yield what you expect.
-- Jörg Barfurth
Would you let me publish your post as a starter for someone else to
flesh it out for lesser mortals?
As a voluntary organisation we do not pay for material but you would get
full credit.
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
Do I sense some irony and resentment here? :-) I am honored to have
people express real interest in my little tricks, which are, of course,
based on what I've learned from the community. I guess I should/shall
write an article for "C++ Report" as Herb suggested to expand on my
template virtual dispatch idiom and possibly other little tricks.
In any case, feel free to use my post in any means to share any ideas
that might be of interest to the community -- to cross-fertilize ideas -
- that's why I participated in the discussions in the first place.
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Wasn't intended. I am always aware that some people do not like their
ideas republished elsewhere even in a publication that relies on
volunteers.
Thanks anyway.
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Well, in reality there is no need in introducing virtual static variables.
Every compiler that implements properties can handle this task with elegance
:-)
Here is the example of a real (BCB) code that does what you want:
class Base
{
public:
static int xcounter;
virtual int Get(){return xcounter;}
virtual void Set(int x){ xcounter=x;}
__property int counter={read=Get,write=Set};
};
int Base::xcounter=1;
class Der: public Base
{
public:
static int xcounter;
virtual int Get(){return xcounter;}
virtual void Set(int x){ xcounter=x;}
};
int Der::xcounter=10;
void foo(Base *o)
{
o->counter++; // depends on dynamic type of o
}
void bar()
{
Base b;
Der d;
foo(&b); // increments Base::counter
foo(&d); // increments Der::counter
cout<<b.counter<<" "<<d.counter<<"\n";
}
Gene Bushuyev
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
On 30 Nov 1999 13:38:04 -0500, merl...@my-deja.com wrote:
> Shucks. Perhaps you can find the time while preparing MEC++, 2nd
> edition? (hint, hint, wheedle)
Hmmm, do you really think it needs to be revised that extensively? In =
my
opinion, the information (in the current printing) is still accurate. =
No?
> > DD is an interesting topic. I'm still on the lookout for a solution
> > that requires minimal recompilation when hierarchies change
> > (naturally, "minimal" means "none", and Visitor fails this test),
> > that's next to foolproof to implement for classes involved in the DD
> > (the need to write class-identification functions would violate this
> > goal), that supports inheritance (unlike the final design in Item =
31),
> > and that localizes changes to a single function when new classes are
> > added to the DD hierarchy or hierarchies (for my design, this was =
the
> > hitmap initialization function). I suspect it is possible to =
develop
> > such a design, I just haven't seen it. Yet.
>
> Wow, you don't ask for much, do you? :-)
On the contrary, I ask for everything. In my opinion, this should be
everybody's design goal all the time: a solution that's easily =
implemented,
understood, and maintained; that's almost impossible to misuse; and =
that's
maximally efficient. Developing such solutions is hard and often takes
many people years of thought and work, so it's not practical to expect =
that
working programmers will develop these solutions on a regular basis.
Still, we should aim for such solutions, and, once we develop them, we
should package them in some way that makes it easy for others to benefit
from our efforts. One form of packaging is code, of course. Another is
patterns. In the near future, I expect to see some really interesting =
work
published that describes how many patterns can be, to some extent, put =
into
C++ constructs. At the recent C++ World conference, for example, =
Angelika
Langer gave a talk on "Strategy and Templates - Using Templates for
Implementation of Patterns," and I know of some other not-yet-published
very advanced and very interesting work in this area. When it gets
published, prepare to have your socks knocked off. If you don't wear
socks, start, because you won't want to miss out on the excitement.
Back on the Item 31 front, both Xavier Tarrago and Acurio mentioned =
Robert
Martin's Acyclic Visitor (ACV) Pattern. I've been acquainted with this
pattern since 1997, when Robert brought it to my attention as an
alternative to my approaches in Item 31. For some reason, I've never =
been
able to generate much enthusiasm for it: all those base classes, all =
that
cross casting. It just rubbed me the wrong way. This thread caused me =
to
look at it again, and I have to say that this time, I like it a lot =
more.
I'm still not totally comfortable with the plethora of base classes and =
the
cross-casting, but I have to admit that it seems to achieve everything =
I'd
like. Unless somebody points out a fatal flaw, I'm inclined to view it =
as
the current best approach to the DD problem, and I'll add an entry to =
the
MEC++ errata to that effect.
On 30 Nov 1999 16:27:20 -0500, Ross Smith wrote:
> I've mentioned my implementation here before; I don't know if you've
> seen it. It places no requirements on the types or functions involved
> other than normal polymorphism. It requires explicit "registration" of
> all of the types and functions involved; this would normally be =
isolated
> in an initialisation function, which is the only code that needs to be
> changed when new classes or functions are added.
My primary objection to this approach is that the user has to manually
recreate the inheritance hierarchy. That's error-prone if the hierarchy
changes a lot. In addition, it just strikes me as misguided: the =
compiler
already knows the hierarchy, so there shouldn't be a need to describe it
all over again. This doesn't mean the approach doesn't work, it just =
means
I like it less than ACV, the current best approach I know of.
On 30 Nov 1999 16:38:44 -0500, Dave Harris wrote:
> sme...@aristeia.com (Scott Meyers) wrote:
> > You'd therefore have to write a custom comparison class
> > for the map:
>
> I would add "in terms of type_info::before()" to the first sentence.
> Otherwise it's just so tempting to compare the pointers, which is =
unsafe.
I'll do this, but heck, I *said* that details were left to the reader =
:-)
Scott
--
Scott Meyers, Ph.D. sme...@aristeia.com
Software Development Consultant http://www.aristeia.com/
Visit http://meyerscd.awl.com/ to demo the Effective C++ CD
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>Back on the Item 31 front, both Xavier Tarrago and Acurio mentioned =
>Robert Martin's Acyclic Visitor (ACV) Pattern. I've been acquainted with
this
>pattern since 1997, when Robert brought it to my attention as an
>alternative to my approaches in Item 31. For some reason, I've never =
>been able to generate much enthusiasm for it: all those base classes,
Hi Scott,
What about just two?
struct Visitor
~Visitor() = 0;
};
template <class T> struct Visit
virtual void visit(T*) = 0;
};
In use you have some base class with a virtual accept method, but
as this is the target - you had that anyway!
class SomeBase {
virtual void accept(Visitor*) = 0;
//...
};
class Derived1 : public SomeBase
void accept(Visitor*);
//...
}:
A ConcreteVisitor is of the form
class ConcreteVisitor : public Visitor,
public Visit<Derived1>, public
Visit<Derived2> ...
{
public:
void visit(Derived1*);
void visit(Derived2*);
//...
};
You do still have to implement all those accept methods but you've lost
all the Derived1Visitor, Derived2Visitor etc headers with pretty much
nothing in them.
cheers
Nick
>all =
>that cross casting. It just rubbed me the wrong way. This thread caused
me =
>to
>look at it again, and I have to say that this time, I like it a lot =
>more.
>I'm still not totally comfortable with the plethora of base classes
>and =
>the
>cross-casting, but I have to admit that it seems to achieve everything =
>I'd
>like. Unless somebody points out a fatal flaw, I'm inclined to view it =
>as
>the current best approach to the DD problem, and I'll add an entry to =
>the
>MEC++ errata to that effect.
>
> > Shucks. Perhaps you can find the time while preparing MEC++, 2nd
> > edition? (hint, hint, wheedle)
> Hmmm, do you really think it needs to be revised that extensively?
> In my opinion, the information (in the current printing) is still
> accurate. No?
Well, I was thinking more in terms of seeing content based on the
"Interesting Comments" on your errata page (which you note won't get
into MEC++ until a second edition, at the earliest).
[ preview of coming attractions -- socked away ]
> Back on the Item 31 front, both Xavier Tarrago and Acurio mentioned
> Robert Martin's Acyclic Visitor (ACV) Pattern. I've been acquainted
> with this pattern since 1997, when Robert brought it to my attention
> as an alternative to my approaches in Item 31. For some reason, I've
> never been able to generate much enthusiasm for it: all those base
> classes, all that cross casting. It just rubbed me the wrong way.
> This thread caused me to look at it again, and I have to say that
> this time, I like it a lot more.
> I'm still not totally comfortable with the plethora of base classes
> and the cross-casting, but I have to admit that it seems to achieve
> everything I'd like. Unless somebody points out a fatal flaw, I'm
> inclined to view it as the current best approach to the DD problem,
> and I'll add an entry to the MEC++ errata to that effect.
I'm confused. Acyclic Visitor gets rid of the need to recompile all of
the Element classes when adding new Visitors, but I don't see how one
can get away from having to modify all of the old Visitor classes
whenever adding a new one. That would seem to violate both your "minimal
recompilation when hierarchies change" and "localizes changes to a
single function when new classes are added" criteria.
ACV also, it seems to me, is limited to supporting *double* dispatch; I
can't imagine how it could be extended to the general n-way multiple
dispatch problem. On the other hand, the map-of-function-pointers
technique *does* generalize easily to the n-way case. On the other
other hand, I am "only an egg" when it comes to C++, and thus am
vulnerable to having my wits scrambled...
--
Edmund Schweppe aka merl...@my-deja.com
Blissfully free of official positions
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Gene Bushuyev wrote:
> Well, in reality there is no need in introducing virtual static
> variables. Every compiler that implements properties can handle
> this task with elegance :-)
> Here is the example of a real (BCB) code that does what you want:
> class Base
> {
> public:
> static int xcounter;
> virtual int Get(){return xcounter;}
> virtual void Set(int x){ xcounter=x;}
> __property int counter={read=Get,write=Set};
> };
[snip]
You might want to search comp.std.c++ (DejaNews) for a post I made a
few months ago demonstrating how to simulate properties in C++ by
overloading the 'operator=()' and 'operator T()' (thus simulating the
'read' and 'write' properties) for a public member variable of type
'T' within a class.
In any case, though, virtual static functions and virtual static
variables probably have their uses.
-- David R. Tribble, da...@tribble.com, http://david.tribble.com --
---
As the next poster had already stated, this is only a logical problem
(viewpoint of the language designer), not a practical problem. In this
type of function (virtual static), you would be unable to call other
virtual static functions, because you have no "this" pointer, even if
the other functions are declared "static", too. I think this is one of
the problems why they didn't allowed this kind of function attribute
combination; they would form a third class of functions which might
be called only by methods (with self-pointer), but can call only
static methods (without self-poiner), and cannot call each other (but
on the other hand can call themselves!). This is ugly, isn't it ?
I would like to have such a feature anyway.
>> Generally you have to select a virtual method first, based on the
>> dynamic type of the object for which the method is invoked, and only
>> then pass it 'this' as an argument. The type of 'this' may be
>> different from both a pointer to the static type and the dynamic
>> (most derived) type of the object. All three pointers could in fact
>> have different values when static_cast'ed to void*. A 'static
>> virtual' method would just be called without passing the this
>> pointer. This would even save any overhead necessary to adjust the
>> value needed for 'this'.
>Exactly. By the same reasoning, C++ could also allow virtual
>static member variables.
Variables are not code, so there is nothing dynamically changeable.
You just want a new instance of them per class, don't you ? I would
really like it if C++ would have a feature like this, declaring code
and variables which are automatically created again for each
subclass, for example:
class object {
public:
virtual typestatic typesize () const { return sizeof(*this); }
};
Well this is still very ugly. But this way you wouldn't have to
reimplement typesize() in subclasses again - only if you want to.
Many of the class declaration macros of frameworks such as wxWindows
(http://www.wxwindows.org) would become superflous this way !
I finally decided to sit down and write some code instead of trying to do
all this stuff in my head. You know, it's amazing how much stuff compiles
and works perfectly in my head but fails miserably when I actually try to
feed it to a compiler...
I started as follows, and yes, I really do write all the comments in as I
write the code. In this case, I figured I'd be posting it in any case, but
commenting as I go also forces me to think about what I'm doing. Thinking
is good for me, and, as this thread has made clear, it's not something I do
as often as I should.
class Collider { // Primary abstract ACV base. Anything
public: // inheriting from "Collider" can collide
virtual ~Collider(){} // with something in the game object
}; // hierarchy
template<class T> // Template for other abstract ACV bases
class CollidesWith { // (per Nick Thurn's nifty suggestion)
public:
virtual void hit(T *TObject) = 0;
};
class GameObject { // Abstract base from Item 31
public:
virtual ~GameObject() {}
};
So far so good. But then I declared SpaceShip:
class SpaceShip: // In Item 31, game objects run into
public GameObject, // one another, so concrete classes
public Collider, // in the element hierarchy are also
public CollidesWith<SpaceShip>, // concrete classes in the visitor
public CollidesWith<SpaceStation>, // hierarchy
public CollidesWith<Asteroid> { ... };
Right away it's clear there is a problem. If I decide to add a new type of
object, say a Satellite, I have to modify the header for every class in my
hierarchy (assuming everything can collide with everything else). That's
clearly unacceptable.
I now have this vague recollection that when I looked at ACV, I concluded
that the primary advantage of the design was when not all Visitor types
make sense for every Element type. That's not the case here, because every
type of object can smack into every other type of object.
Furthermore, as this example shows, it's not always the case that the
Visitor and Element hierarchies are distinct. That can make a big
difference, as it does in this case.
> ACV also, it seems to me, is limited to supporting *double* dispatch; I
> can't imagine how it could be extended to the general n-way multiple
> dispatch problem. On the other hand, the map-of-function-pointers
> technique *does* generalize easily to the n-way case.
Visitor and its variants should generalize without much trouble, because
you just use one virtual call for each participant in the interaction. If
there are N participants, you use N virtual calls, each taking N-1
parameters (plus the "this" pointer). One paper that I know discusses this
is http://ourworld.compuserve.com/homepages/Patrice_Gautier/visitor.pdf,
but it's been a while since I read it, so caveat emptor.
As for the map of function pointers, I still like it. I'd just like it a
lot more if it supported inheritance.
>You might want to search comp.std.c++ (DejaNews) for a post I made a
>few months ago demonstrating how to simulate properties in C++
"david tribble properties" (or "... property", "... simulate") turns up a
few hits, but no demonstration of implementing properties.
Could you be a bit more specific?
Thanks!
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
That's why I think that the collision code (and in general the
multiple-dispatch code) should be centralized apart from the classes
ot operates with.
> As for the map of function pointers, I still like it. I'd just like
it a
> lot more if it supported inheritance.
So do I, and exaclty for the reason above. Besides, it's the one
that's the most flexible at runtime.
Andrei
[ snippage regarding acyclic visitors and multiple dispatch ]
> > ACV also, it seems to me, is limited to supporting *double*
> > dispatch; I can't imagine how it could be extended to the
> > general n-way multiple dispatch problem. On the other hand,
> > the map-of-function-pointers technique *does* generalize
> > easily to the n-way case.
> Visitor and its variants should generalize without much trouble,
> because you just use one virtual call for each participant
> in the interaction. If there are N participants, you use N
> virtual calls, each taking N-1 parameters (plus the "this"
> pointer). One paper that I know discusses this is
> http://ourworld.compuserve.com/homepages/Patrice_Gautier/visitor.pdf,
> but it's been a while since I read it, so caveat emptor.
Thanks for the pointer; I'll take a look.
> As for the map of function pointers, I still like it. I'd just like
> it a lot more if it supported inheritance.
Well, the map itself neither support nor prevents supporting
inheritance; it's how you create the map *keys* that's the tricky part.
Some possibilities for creating map keys are:
1) use some property of type_info, in which case you could fake
inheritance by adding map entries for all the subclasses;
2) use a virtual method, which clueless class authors can screw up
3) use a standalone function that recreates the inheritance hierarchy
(with all the maintenance headaches that would entail).
None of these are *perfect*, but you pays your money and you takes your
choice.
--
Edmund Schweppe aka merl...@my-deja.com
Blissfully free of official positions
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
"virtual static" methods can call other "virtual static" methods. The
only question is whether or not they should do so implicitly. In other
words, in the virtual static method should you have to fully qualify
calls to other virtual static methods, or will the compiler implicitly
assume the same type and call the function statically? The implicit
route may result in (at first) surprising behavior, but it's no
different than what you'd achieve using the fully qualified method.
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! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
Stefan Hoffmeister wrote:
> "david tribble properties" (or "... property", "... simulate") turns
> up a few hits, but no demonstration of implementing properties.
>
> Could you be a bit more specific?
It was posted 1999-07-01 in comp.lang.moderated.c++ and comp.std.c++
under the subject 'Re: Are there any plans to make "properties"
standard?'
To save some unnecessary internet traffic, I'll simply repost it
here...
------------------------------------------------------
Lisa Lippincott wrote:
> In my experience, properties are an essential abstraction for
> connecting objects within a program to external interfaces. In
> addition to Borland's library, I cite Apple's AppleScript and
> NeXT's Interface Builder as examples of this use.
I haven't been following this thread very closely, but it seems to me
that something resembling "property closures" can be written using
templates (assuming I understand the meaning of "closures" in the
context of this thread):
// Warning: Untested code
template <class T>
class Closure
{
public:
~Closure();
Closure(T & (*read)(T &o),
void (*write)(T &o, const T &r));
T & read() const; // Get the value
void write(const T &r); // Set the value
T & operator =(const T &r); // Assign the value
operator T() const; // Retrieve the value
private:
T val; // The value
T & (*readf)(T &o); // Value reading func
void (*writef)(T &o, const T &r);
// Value writing func
};
template <class T> inline
T Closure<T>::operator T() const
{
// Read the value
return (*readf)(*this);
}
template <class T> inline
T & Closure<T>::operator =(const T &r)
{
// Write the value
(*writef)(*this, r);
return *this;
}
Now we can declare a class with a member of type 'Closure<float>'
which has a "reader" and "writer" functions associated with it:
class Example
{
public:
Closure<float> color;
private:
float & getColor(float &o);
void setColor(float &o, const float &v);
};
Example::Example():
color(&getColor, &setcolor) // Establish reader/writer
// funcs for .color
{
...
}
(Note that member 'color' contains a float value as well as the
overhead of two function pointers.)
Now we can access the 'color' member of an 'Example' object, which
indirectly invokes the reader and writer functions:
Example ex;
float f;
ex.color = 1.5; // Calls color.operator =(),
// which calls color.setColor()
f = ex.color; // Calls color.operator T(),
// which calls color.getColor()
This would be even better if we could find a way of eliminating the
overhead of the two function pointers (which might be possible if
the reader and writer functions are static member functions).
Or am I on the wrong track here?
-- David R. Tribble, da...@tribble.com, http://david.tribble.com --
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
In this case, you can use an adapting collision vistor, instead of
obj1.hit(obj2 which is an instance of collider), use obj1.hit(an
instance of collider for obj2);
an instance of collider for obj2 might be instantiated as
CollisionVistor<obj2 type or obj2's base type>(obj2);
where:
template <class T>
CollisionVistor : public Collider, public CollidesWith<...> {...};
specializations can be provide for individual cases.
When you add a new object, only some of the visiting adaptors (not
objects) need to be changed, just like you have to register new
functions for new collisions in map implementations.
> As for the map of function pointers, I still like it. I'd just like
it a
> lot more if it supported inheritance.
These global functions can also be viewed as a kind of poor man's
adaptor since they usually in turn call the public interface of the
interacting objects to do anything interesting. However, you have to
rely on a virtual id special to the operation to support inheritance in
an error prone manner. Every time you add a new operation like "trade",
you potentially need a new virtual id for every participating class to
support inheritance, as the overriding scheme may be different, e.g., a
CommercialShip may collide like a SpaceShip but definitely trade like a
CommercialShip. Visiting adaptors have no such problems and still more
time and space efficient than maps of function pointers.
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> You might want to search comp.std.c++ (DejaNews) for a post I made a
> few months ago demonstrating how to simulate properties in C++ by
> overloading the 'operator=()' and 'operator T()' (thus simulating the
> 'read' and 'write' properties) for a public member variable of type
> 'T' within a class.
Right. C++ doesn't need properties; Borland Pascal needs them because it
has no operator overloading. Like some other oddities, Borland introduced
built-in properties into their C++ dialect to get binary compatibility with
libraries originally written for Delphi.
- Wil
Wil Evers, DOOSYS IT Consultants, Maarssen, Holland
[Wil underscore Evers at doosys dot com]
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
ftp://animal.ihug.co.nz/pub/dispatch2.tgz
This one uses dynamic_cast on pointers to interrogate the inheritance
hierarchy at run time. It makes no explicit use of typeid (although
presumably the compiler is inserting the equivalent behind the scenes to
make dynamic_cast work).
I think it meets all of Scott's wish list:
* "Requires minimal recompilation when hierarchies change (naturally,
"minimal" means "none", and Visitor fails this test)" -- Adding new
classes to the hierarchy (without adding any new functions) requires no
changes at all to existing code.
* "Next to foolproof to implement for classes involved in the DD (the
need to write class-identification functions would violate this goal)"
-- It places no requirements on the classes involved beyond normal
polymorphism.
* "Supports inheritance (unlike the final design in Item 31)" -- It
doesn't require an exact match on argument types; the dynamic types of
the actual arguments can be descendants of the formal argument types of
a registered function. If there is more than one function that fits, the
last one registered is called (this seemed the simplest way to resolve
it, since functions added in later revisions are likely to be more
specialised than earlier ones, so you add them at the end of the
registration sequence and the right one will be selected).
* "Localizes changes to a single function when new classes are added to
the DD hierarchy or hierarchies (for my design, this was the hitmap
initialization function)" -- Adding new classes requires no changes at
all. Adding new functions to be dispatched requires only the addition of
a single registration call.
(Its main disadvantage from my point of view is that it requires partial
specialisation and therefore won't work with two of the three compilers
I use (Microsoft and Sun).)
--
Ross Smith <ros...@ihug.co.nz> The Internet Group, Auckland, New Zealand
========================================================================
"Be careful about using the following code -- I've only proven
that it works, I haven't tested it." -- Donald Knuth
The question of what C++ needs doesn't really make much sense. We can only
speak about what programmers need (and what compiler implementors can/want)
in order to express some concepts; and this "need" is often very subjective.
Speaking of myself, I find properties much needed.
There was a number of discussions about properties in the past. There were
some suggestions of implementation. I haven't seen yet an implementation
that would have the full functionality of Borland (or even MS VC) properties
and have the same efficiency and convenience of use. If you want to stay
with the standard you shouldn't try to implement property (class) rather you
should use the functions (like Get, Set, and maybe other) instead. If you
have something new to offer, I would be glad to see that.
Gene Bushuyev
See following comments on "supports inheritance".
>
> * "Next to foolproof to implement for classes involved in the DD (the
> need to write class-identification functions would violate this goal)"
> -- It places no requirements on the classes involved beyond normal
> polymorphism.
>
> * "Supports inheritance (unlike the final design in Item 31)" -- It
> doesn't require an exact match on argument types; the dynamic types of
> the actual arguments can be descendants of the formal argument types
of
> a registered function. If there is more than one function that fits,
the
> last one registered is called (this seemed the simplest way to resolve
> it, since functions added in later revisions are likely to be more
> specialised than earlier ones, so you add them at the end of the
> registration sequence and the right one will be selected).
This an error prone process and a severe scalability bottle neck.
because you have to keep the registrations in a particular order, you
have to put all the run-time registration code in one file (the self
registering initializer object trick won't work because the order of
initialization from different compilation units is arbitrary). This
implies that whenever you add a new function you have to recompile at
least one existing file. It also precludes a dynamically loadable
component architecture, where only required components are loaded and
registered in arbitrary order.
>
> * "Localizes changes to a single function when new classes are added
to
> the DD hierarchy or hierarchies (for my design, this was the hitmap
> initialization function)" -- Adding new classes requires no changes at
> all. Adding new functions to be dispatched requires only the addition
of
> a single registration call.
>
> (Its main disadvantage from my point of view is that it requires
partial
> specialisation and therefore won't work with two of the three
compilers
> I use (Microsoft and Sun).)
See above comment. The fatal flaw of the solution that would prevent me
from using it as an extension mechanism in a framework is that
basically it doesn't scale very well and can be very inefficient (2n
dynamic_casts PER call, n is number of registered functions) when
number of registered functions is large.
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Carlo Pescio
"Multiple Dispatch: A New Approach Using Templates and RTTI"
_C++ Report_, June 1998
provides, IMHO, a very complicated method. I'd like to hear any pros and
cons on that method.
Okay, the qualified way works. But not the implicit way. You call a
virtual function and its suddenly no longer virtual, because you have
no 'this pointer' ? This is impossible, that rule would be completely
confusing ! You cannot call virtual functions without an object,
point.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
I didn't say programmers don't need properties. What I did try to say is
that C++ allows us to implement properties as a library facility; there's
no need to change the language definition for that.
I can't believe that the people who decided to introduce built-in
properties into Borland C++ weren't aware of this; they must have felt that
it was cheaper to change their C++ compiler than to reimplement the VCL in
C++.
I believe that in doing so, they served Borland's interests rather than the
C++ community as a whole, because C++ is big enough without properties.
> There was a number of discussions about properties in the past. There
> were some suggestions of implementation.
Yes. I was referring to David R. Tribble's example implementation.
> I haven't seen yet an implementation that would have the full
functionality
> of Borland (or even MS VC) properties and have the same efficiency and
> convenience of use.
If we loosely define a property as a device that maps a setter/getter pair
to a named class member, then it can be implemented fairly cheaply in terms
of run-time overhead.
As always, getting the full bag of syntactic sugar is harder without
changing the compiler. I don't believe that's much of a problem as long as
most of the burden is on the class's implementor, rather than on the
class's users.
When you talk about functionality and ease of use, what exactly are you
looking for?
> If you want to stay with the standard you shouldn't try to implement
> property (class) rather you should use the functions (like Get, Set,
> and maybe other) instead.
Why?
> If you have something new to offer, I would be glad to see that.
Other than the usual C++ bag of tricks, I don't think so, which is exactly
the point I'm trying to make.
Regards,
- Wil
Wil Evers, DOOSYS IT Consultants, Maarssen, Holland
[Wil underscore Evers at doosys dot com]
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
There are lots of things that C++ can live without. I think what your are
talking is expressed by the words "necessary to achieve a certain goal,"
rather than needed. But this is also debatable, since the "goal" is not
objectively defined; as you may notice the standard doesn't often answer the
question "why?"
Even if it were true that the properties can be implemented by the existing
means it doesn't prove they shouldn't be introduced in the language. There
are many things that are redundant in C++ and nobody suggests they should
not be there.
[snip]
> > There was a number of discussions about properties in the past. There
> > were some suggestions of implementation.
>
> Yes. I was referring to David R. Tribble's example implementation.
It's not an implementation. Firstly, it doesn't work. Secondly, ... well I
will leave it till the time when a working implementation will be presented
:-) So if you have something particular to offer I would be interested in
seing that.
[snip]
> If we loosely define a property as a device that maps a setter/getter pair
> to a named class member, then it can be implemented fairly cheaply in
terms
> of run-time overhead.
It's quite obvious that a Property class will always be (much) slower than
built-in properties. Yes, you can use such class in non-critical places in
your program. Otherwise, one must use Get/Set functions and inline them if
possible to avoid overhead.
And how about an array of properties that are also built-in in Borland? You
wouldn't certainly like to use an array of Property classes. So you need a
different ArrayOfProperties class. I'd like to see that implementation also.
Since all the efforts I have seen to implement properties were using
templates, it immediately imposes all the limitations templates have (see
14.3.1) and built-in properties do not have.
I believe I can come up with more examples that would be difficult (maybe
even impossible to implement) with the Property template class, but I will
wait till the moment somebody presents a workable implementation.
--
Gene Bushuyev
Entia non sunt multiplicanda praeter necessitatem
-mi
--
Micha Berger (973) 916-0287 MMG"H for 20-Dec-99: Levi, Vayechi
mi...@aishdas.org A"H
http://www.aishdas.org Pisachim 86b
For a mitzvah is a lamp, and the Torah its light.
---
If written by the user, but the compiler implementor could add some
'magic' that removes that overhead. I think that is the 'correct'
solution, my code would be portable but I get extra benefit from using
xyz compiler.
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Make it a static member of a virtual accessor function, of course.
Just remember to return a *reference* to it.
I.e.
class A {
virtual std::string& ClassId() const
{ static std::string s;return s; }
}
Michiel Salters
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
I don't follow. If everything can collide with everything else, it looks
to me like the header containing the CollisionVisitor template must be
modified each time a new type of object, T, is added (because of the public
inheritance from CollidesWidth<T>). Now all code initiating a collision
must recompile if a new type is added. It's not clear to me that this is
much of a win.
I think there is. Here's a comparison chart of DD solutions that I know of.
All of them are "librarizable". Comments/additions/suggestions welcome.
By hand: two virtuals, or one virtual plus a switch-on-type, or a
double-switch-on-type (wow). Every class in the hierarchy depends on every
other class. This also is the fastest.
The GoFV (GoF Visitor) solution: classes still depend on each other, but now
you can use the same mechanism for other purposes than collision. Fast.
The ACV-based solution: classes are independent, but the collision code
depends on all classes. This is slightly better than above in terms of
dependencies, but is also slightly less efficient: two extra dynamic-casts
per call. In addition, you can use ACV for other purposes. Note that the
number of functions that can be fired from the dispatch engine is
essentially finite, known at compile-time. I discovered this is an essential
criterion when evaluating DD solutions.
The map-and-function solution: highly decoupled. Less efficient - a map
lookup per call. Doesn't support inheritance. It supports universal
multi-object polymorphism, as the number of function manipulated is
theoretically unbounded. I found a trick that makes the approach faster (a
direct call instead of a call-through-pointer) and also less clumsy than in
Item 31 (the casts are made by the engine, not user code). All - please let
me know if interested.
The map-and-functor approach: like above, but with a second vtable call.
Advantage - you can hold state.
The vector-and-function approach: highly decoupled, offers universal
multi-object polymorphism. O(N) complex in registered functions, but
supports inheritance. Has problems wrt order of registration.
The vector-and-functor approach - like above, but supports keeping state.
Less efficient - an extra vtable call.
I think there is no one solution to the problem. A DD library should
implement all and let the user pick the one that best suits the problem at
hand.
Andrei
You can solve the problem with a CollisionVistor Factory.
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
By the following logic regarding map-function solution, the number of
function manipulated is also unbounded if the code use a vistor factory
to construct vistor objects. You can even put elements and vistors in
dynamically loaded components. The number of functions that can be
fired is definitely not known at compile time. ACV like solution (you
don't have to use the MI/dynamic_cast approach) is my current favorite
as it supports inheritance seamlessly and that the vistors can hold
states. It's currently also the fastest extensible solution.
>
> The map-and-function solution: highly decoupled. Less efficient - a
map
> lookup per call. Doesn't support inheritance. It supports universal
> multi-object polymorphism, as the number of function manipulated is
> theoretically unbounded.
the messy support for inheritance (that initiated the original thread)
is the reason (plus the poor performance) I don't use it in my current
projects.
>I found a trick that makes the approach faster (a
> direct call instead of a call-through-pointer) and also less clumsy
than in
> Item 31 (the casts are made by the engine, not user code). All -
please let
> me know if interested.
Yes, I'm interested in the 'direct-call' solution, mostly because I
don't really understand what you mean. :-)
> The map-and-functor approach: like above, but with a second vtable
call.
> Advantage - you can hold state.
>
> The vector-and-function approach: highly decoupled, offers universal
> multi-object polymorphism. O(N) complex in registered functions, but
> supports inheritance. Has problems wrt order of registration.
>
> The vector-and-functor approach - like above, but supports keeping
state.
> Less efficient - an extra vtable call.
>
> I think there is no one solution to the problem. A DD library should
> implement all and let the user pick the one that best suits the
problem at
> hand.
Agreed.
void Fun();
Direct call:
Fun();
Call-through-pointer:
void (*pFun)() = Fun;
(*pFun)();
Still interested?
Andrei
Stop teasing, show us the map to direct call mechanism please, although
I suspect an average map lookup (including key construction) takes much
more time than the time you saved through the 'direct call'...
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I think you're right.
The idea is that you can pass a pointer to a function as a template
parameter, so:
class Dispatcher
{
...
template <class T, class U, void (&Fun)(T&, U&)>
Register()
{
struct Local
{
void Trampoline(Shape& lhs, Shape& rhs)
{
// compile-time assertion that T and U are derivees of Shape
CT_ASSERT(SUPERSUBCLASS(Shape, T));
CT_ASSERT(SUPERSUBCLASS(Shape, U));
Fun(static_cast<T&>(lhs), static_cast<U&>(rhs));
}
};
theMap.insert(MakeKey(typeid(T), typeid(U)), &Local::Trampoline);
}
...
};
Use:
namespace
{
void HitSpaceshipAsteroid(Spaceship&, Asteroid&) { ... }
}
Dispatcher engine;
engine.Register<Spaceship, Asteroid, HitSpaceshipAsteroid>();
The idea here is that you can build a trampoline that does the call without
an additional indirection. I don't know exactly whether this code is
correct, but I guess it is (I gave up a long time ago trying to get this
answer from an actual compiler :o)). I would be greatful if you commented on
the code's correctness: can you call a pointer/reference function passed as
a template argument, from a local function?
Note that things are pretty robust right now, so there's no need for
dynamic_cast anymore.
I like this trampoline thing a lot. Traditionally, trampolines were compiler
magic. This is a genuine trampoline built with function templates.
Andrei
You didn't show us how to initiate the interaction. Looks like you're
storing pointers to Local::Trampoline in the map. It seems you have to
call the hit functions through the pointers to trampolines. Although
you added an indirection (trampoline with type checkings) without
overhead of addition function pointer, you still basically calls the
hit functions through a pointer, local or not. I don't see any saving
here over storing pointers to hit functions directly.
>
> The idea here is that you can build a trampoline that does the call
without
> an additional indirection. I don't know exactly whether this code is
> correct, but I guess it is (I gave up a long time ago trying to get
this
> answer from an actual compiler :o)). I would be greatful if you
commented on
> the code's correctness: can you call a pointer/reference function
passed as
> a template argument, from a local function?
Yes. I use it all the time.
> Note that things are pretty robust right now, so there's no need for
> dynamic_cast anymore.
This is actually how I implemented ACV without dynamic_cast and rtti
support.
> I like this trampoline thing a lot. Traditionally, trampolines were
compiler
> magic. This is a genuine trampoline built with function templates.
template gives programmers (esp, library writer) a very useful magic:
adding indirection without (much) overhead of indirection from normal
non-inlined wrappers (which can cause more instruction cache misses
because of memory fragmentation).
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
There's a slight misunderstanding. I think it's because I
misformulated my post.
My claim was that I achieved type conversion (the casts) without an
extra function call. But you still have to do a call-through-pointer.
It's only good you don't have to do two :o).
> > I would be greatful if you
> commented on
> > the code's correctness: can you call a pointer/reference function
> passed as
> > a template argument, from a local function?
>
> Yes. I use it all the time.
I think the correct question is: can you call a pointer/reference
function passed as a template argument, from a local class' member
function?
If you use that all the time, you da man!
Andrei
Two questions:
1. Why isn't Trampoline static in Local?
2. Why is Fun a reference instead of a pointer?
Thanks,
Scott
--
Scott Meyers, Ph.D. sme...@aristeia.com
Software Development Consultant http://www.aristeia.com/
Visit http://meyerscd.awl.com/ to demo the Effective C++ CD
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
But at what expressive cost? Using the example in Item 31, suppose the
player of the game has just done something nifty, and the game decides to
reward him/her by giving him/her an additional spaceship. I'd expect to
see this in the code somewhere:
SpaceShip *newShip = new SpaceShip;
... // possibly do some spaceship-
// specific stuff
With a factory, my guess is that the above code will look a lot less
obvious. No?
I've used it in global/static/member template functions, compiler
support for the latter (member template function) is not yet available
on every platform I use though.
A.
Sent via Deja.com http://www.deja.com/
Before you buy.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
The above code will not change at all. The only difference is in
collision invocation:
instead of saying:
newShip->collide(oldShip);
say:
newShip->visit(collideVistor(warShip));
where:
collideVistor() is something like:
template <class T> GameObjectVistor *
collideVistor(T *obj) { return CollideVistorFactory<T>::visitor(obj); }
The advantage of this approach is obvious: decoupled polymorphic
interaction, as it's trivial to add:
newShip->visit(tradeVistor(commerceShip));