 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Thomas Hansen Guest
|
Posted: Mon Aug 09, 2004 9:37 am Post subject: Hyperactive static_assert, or what's not there... |
|
|
I was writing some code a couple of days ago when I came up with this
really nifty thing, I thought it was so nifty and of general use that
I had to write about it in my blog...
I don't think I've seen too much of this concept before, Andrei
Alexandrescu plays around with it a bit in Policy Based Design and
CompileTimeAssert, but not to such an extend as I do in these
classes...
Here is a copy of my entire blog about the subject:
[blog]
The joy of the things that's not there or the Circle/Ellipsis problem
II...
Think of a class like for instance a Geometric object...
Then imagine this object having the possibilities of being implemented
in two different terms, Circle and Ellipsis.
Most newbies to OOP would try to make the Circle a subclass of the
Ellipsis class which off course is a BAD thing to do, think of like
for instance the setWidth function, would it set both the width and
the height of the Circle, then what would the setHeight function do?
All OOP tutors (at least the good ones) teach this as their basic
lesson
And that is basically the Circle/Ellipsis problem explained the easy
way...
Well there is a way to do it and keeping the (kind of) Ellipsis class
as a base member...
WithOUT breaking all the good OOP rules...
Solution:
"What's not there!"
class Circle
{
protected:
enum CanSetRadius { OhhYesWeCan };
};
class Ellipsis
{
enum CanSetHeight { OhhYesWeCan };
enum CanSetWidth { OhhYesWeCan };
};
template<class Type>
class GeometricObject
{
public:
void setWidth( int newWidth )
{
CanSetWidth;
itsWidth = newWidth;
}
void setHeight( int newHeight )
{
CanSetHeight;
itsHeight = newHeight;
}
void setRadius( int newRadius )
{
CanSetRadius;
itsRadius = newRadius;
}
};
Well, you're probably asking; "what's so beautifule with that?"
The beauty comes with instantiation!
GeometricObject<Circle> circle;
GeometricObject<Ellipsis> circle;
Now try to set the width of your Circle...
....IT WON'T COMPILE!
Hooray!
....but WHY!
Because it's trying to get to something which is NOT THERE!
The beauty of what is NOT there...
Basically you're referencing the "CanSetWidth" inside your setWidth
function which btw comes with ZERO runtime overhead...
While this WILL work for an Ellipsis it WON'T work for a Circle!
Since the Circle doesn't HAVE that member!
Oh yeah, did I tell you it comes with ZERO runtime overhead?
The compiler will optimize the variable away since it's a native
unnamed type never de-referenced
The Circle/Ellipsis problem revisited, slain, eaten, consumed and
totally havoced!
While this is a trivial example and probably a BAD example too and a
VERY lousy interface of a Circle and Ellipsis class it still
illustrates some rather cool concepts.
The whole idea lies in; "What's not there!"...
And we could probably find LOTS of nice uses for such a solution...
..t
[/blog]
Thomas Hansen
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Thomas Hansen Guest
|
Posted: Tue Aug 10, 2004 6:46 pm Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
[email]polterguy (AT) gmail (DOT) com[/email] (Thomas Hansen) wrote in message news:<101a1348.0408082259.79ef46c9 (AT) posting (DOT) google.com>...
[snip]
I would appreciate comments on this, is this something I am the first
to think of or is this "old knowledge" among the gurus...
..t
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Bronek Kozicki Guest
|
Posted: Wed Aug 11, 2004 10:46 am Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
Thomas Hansen wrote:
| Quote: | polterguy (AT) gmail (DOT) com (Thomas Hansen) wrote in message news:<101a1348.0408082259.79ef46c9 (AT) posting (DOT) google.com>...
[snip]
I would appreciate comments on this, is this something I am the first
to think of or is this "old knowledge" among the gurus...
|
you forgot small part of code:
template<class Type>
class GeometricObject // : public Type ??? or something else ?
{
public:
void setWidth( int newWidth )
{
CanSetWidth; // or maybe Type::CanSetWidth; ???
itsWidth = newWidth; // where "itsWidth" comes from ?
} // ... etc ...
without it it's quite difficult to justify exact meaning of your
solution. If you use inheritance from Type, enumerations are redudant:
class Circle
{
protected:
int itsRadius;
};
class Ellipsis
{
protected:
int itsHeight;
int itsWidth;
};
template<class Type>
class GeometricObject : public Type
{
public:
void setWidth(int newWidth)
{
this->itsWidth = newWidth;
}
void setHeight( int newHeight )
{
this->itsHeight = newHeight;
}
void setRadius( int newRadius )
{
this->itsRadius = newRadius;
}
};
int main()
{
GeometricObject<Ellipsis> e;
e.setWidth(20);
// e.setRadius(30); // boom!
GeometricObject<Circle> c;
c.setRadius(10);
// c.setHeight(15); // boom!
}
If "itsWidth" comes from some other source (not base class) then
GeometricObject will look different, but enumeration is probably still
redudant - whenever you refer to some field (or function) of template
parameter, it must be there anyway.
B.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Wed Aug 11, 2004 7:48 pm Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
Thomas Hansen <polterguy (AT) gmail (DOT) com> wrote:
[]
| Quote: | Think of a class like for instance a Geometric object...
Then imagine this object having the possibilities of being implemented
in two different terms, Circle and Ellipsis.
Most newbies to OOP would try to make the Circle a subclass of the
Ellipsis class which off course is a BAD thing to do, think of like
for instance the setWidth function, would it set both the width and
the height of the Circle, then what would the setHeight function do?
All OOP tutors (at least the good ones) teach this as their basic
lesson
And that is basically the Circle/Ellipsis problem explained the easy
way...
|
You might want to check almost perfect (IMO) solution to the problem by Kevlin Henney - http://www.cuj.com/documents/s=7997/cujcexp1903henney/
| Quote: | Well there is a way to do it and keeping the (kind of) Ellipsis class
as a base member...
WithOUT breaking all the good OOP rules...
|
Well, not quite so. See bellow.
| Quote: | Solution:
"What's not there!"
class Circle
{
protected:
enum CanSetRadius { OhhYesWeCan };
};
class Ellipsis
{
enum CanSetHeight { OhhYesWeCan };
enum CanSetWidth { OhhYesWeCan };
};
template<class Type
class GeometricObject
{
public:
void setWidth( int newWidth )
{
CanSetWidth;
itsWidth = newWidth;
}
void setHeight( int newHeight )
{
CanSetHeight;
itsHeight = newHeight;
}
void setRadius( int newRadius )
{
CanSetRadius;
itsRadius = newRadius;
}
};
|
Interface is a fundamental concept in OOP. It tells you what you can do with the object. The GeometricObject<> interface lies to the user - it exposes functions while some of them won't even compile. So, you broke a major OOP rule.
| Quote: | Well, you're probably asking; "what's so beautifule with that?"
The beauty comes with instantiation!
GeometricObject<Circle> circle;
GeometricObject<Ellipsis> circle;
|
Try explicit instantiation to see the ugliness:
template class GeometricObject<Circle>;
In my opinion, apart from the code being ill-formed, the idea is fundamentally flawed.
--
Maxim Yegorushkin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Thomas Hansen Guest
|
Posted: Thu Aug 12, 2004 11:56 am Post subject: Re: [LONG]Hyperactive static_assert, or what's not there... |
|
|
Bronek Kozicki <brok (AT) rubikon (DOT) pl> wrote
| Quote: | Thomas Hansen wrote:
[email]polterguy (AT) gmail (DOT) com[/email] (Thomas Hansen) wrote in message news:<101a1348.0408082259.79ef46c9 (AT) posting (DOT) google.com>...
[snip]
I would appreciate comments on this, is this something I am the first
to think of or is this "old knowledge" among the gurus...
you forgot small part of code:
template<class Type
class GeometricObject // : public Type ??? or something else ?
|
Off course inheritence.
Bummer by me, just forgot to put it in.
| Quote: | {
public:
void setWidth( int newWidth )
{
CanSetWidth; // or maybe Type::CanSetWidth; ???
|
Also totally right!
Another bummer...
| Quote: | itsWidth = newWidth; // where "itsWidth" comes from ?
|
third bummer!
(blush)
I think I need to run my samples through a compiler before i submit
them.
| Quote: | } // ... etc ...
without it it's quite difficult to justify exact meaning of your
solution. If you use inheritance from Type, enumerations are redudant:
class Circle
{
protected:
int itsRadius;
};
|
I (think I) disagree here.
| Quote: |
class Ellipsis
{
protected:
int itsHeight;
int itsWidth;
};
|
I (think I) disagree here too.
| Quote: |
template
class GeometricObject : public Type
{
public:
void setWidth(int newWidth)
{
this->itsWidth = newWidth;
|
I think you need the enums here.
Se further down for an explenation of why.
[snip]
| Quote: | If "itsWidth" comes from some other source (not base class) then
GeometricObject will look different, but enumeration is probably still
redudant - whenever you refer to some field (or function) of template
parameter, it must be there anyway.
|
Actually kind of like the point was for the base classes to be empty
since I wanted them to provide only "Contracts" and nothing else, a
good example of why I think this is a better solution is that radius
may be implemented like:
void setRadius( int newRadius )
{
itsWidth = itsHeight = newRadius;
}
And therefor we need to have the enum since the only data members are
shared!
If I were to supply three "complete" classes I would probably have
chosen to do it like this:
struct Circle
{
enum CanSetRadius {OhhYesWeCan};
};
struct Ellipsis
{
enum CanSetWidth {OhhYesWeCan};
enum CanSetHeight {OhhYesWeCan};
};
template<class Type>
class GeometricObject : public Type
{
int itsWidth;
int itsHeight;
int itsXPos;
int itsYPos;
public:
void setX( int x )
{
itsXPos = x;
}
void setY( int y )
{
itsYPos = y;
}
void setWidth(int newWidth)
{
Type::CanSetWidth;
itsWidth = newWidth;
}
void setHeight( int newHeight )
{
Type::CanSetHeight;
itsHeight = newHeight;
}
void setRadius( int newRadius )
{
Type::CanSetRadius;
itsWidth = itsHeight = newRadius;
}
};
int main()
{
GeometricObject<Ellipsis> e;
e.setWidth(20);
// e.setRadius(30); // boom!
GeometricObject<Circle> c;
c.setRadius(10);
// c.setHeight(15); // boom!
}
And basically the whole idea is to have the expression powers to do
things as you want to do and not being forced into an "unatural" OOP
model...
After all a Circle IS a specialization of an Ellipsis, even if it
doesn't match the OOP model.
That's how we percieve a Circle and therefor it should be like that in
OOP too.
Also you would probably want to share lots of the rendering logic,
like for instance the function for rendering an ellipsis (and therefor
a Circle too) (ref, Once And Once Only) without being forced to bring
in further generalized classes (RoundishObject which you would have to
do otherwise)
Maybe even if we wanted to go ALL the way and do really creative
things we could maybe provide an enum and Int2Type (ref. Andrei
Alexandrescu) and make the Type class become a template class itself
and then do specializations upon Int2Type?
But I haven't been thinking too much about that, it just popped up
right now when I was writing so it might show up to be a really bad
idea when I get to think about it.
Maybe something like this for rendering?
template<class Type>
ScreenBuffer& operator << ( ScreenBuffer &, const
GeometricObject
Implemented some other place.
And off course forward declared over the class and brought in as a
friend to the GeometricObject class.
Then if we had access to a graphic card which could do hardware
accellerated Ellipsis rendering the function could be specialized for
Type == Circle
It probably had to be specialized anyway since the different objects
have different members, e.g. Rectangle doesn't have itsWidth/itsHeight
etc.
But anyway the point is that I think I have come up with a way to do
true "Design By Contract" during compile time.
Sorry for all the bugs in my OP!
It definitively made my original thoughts very blury.
Thomas Hansen
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Tokyo Tomy Guest
|
Posted: Thu Aug 12, 2004 12:13 pm Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
[email]polterguy (AT) gmail (DOT) com[/email] (Thomas Hansen) wrote in message news:<101a1348.0408100627.31b0f2b4 (AT) posting (DOT) google.com>...
| Quote: | polterguy (AT) gmail (DOT) com (Thomas Hansen) wrote in message news:<101a1348.0408082259.79ef46c9 (AT) posting (DOT) google.com>...
[snip]
I would appreciate comments on this, is this something I am the first
to think of or is this "old knowledge" among the gurus...
|
Thomas Hanse, Your code is not perfect for test. Make it perfect, and
you will find problems in your idea, if any.
I modified your code as shown below ( sorry for ugly code). I found
that enums were not required at all for static_assert. Maybe,
something is missing.
class Circle
{
public:
enum CanSetRadius { OhhYesWeCan };
double itsRadius;
};
class Ellipsis
{
public:
enum CanSetHeight { OhhYesWeCan1 };
enum CanSetWidth { OhhYesWeCan2 };
double itsHeight;
double itsWidth;
};
template<class Type>
class GeometricObject
{
public:
void setWidth( int newWidth )
{
Type::CanSetWidth;
kindOfEllipsis.itsWidth = newWidth;
}
void setHeight( int newHeight )
{
Type::CanSetHeight;
kindOfEllipsis.itsHeight = newHeight;
}
void setRadius( int newRadius )
{
Type::CanSetRadius;
kindOfEllipsis.itsRadius = newRadius;
}
Type kindOfEllipsis;
};
int main(int argc, char* argv[])
{
GeometricObject<Circle> circle;
GeometricObject<Ellipsis> ellipsis;
circle.setRadius(10); //ok
ellipsis.setWidth(10); // ok
ellipsis.setHeight(10); //ok
circle.setWidth(10); // error, but even if without enum
return 0;
}
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Thomas Hansen Guest
|
Posted: Fri Aug 13, 2004 11:00 am Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
"Maxim Yegorushkin" <e-maxim (AT) yandex (DOT) ru> wrote
Interesting article and an adequate solution for the setter problem,
but I think that having a "semi_major_axis" and a "semi_minor_axis" in
a Circle class is almost equally as bad as having the setter
functions.
After all the circle has got a radius property and doesn't divide into
the axis like an ellipse.
| Quote: |
[snip]
Interface is a fundamental concept in OOP. It tells you what you can do with the object. The GeometricObject<> interface lies to the user - it
exposes functions while some of them won't even compile. So, you broke a major OOP rule. |
The GeometricalObject could still inherit from an abstract basclass
having functions like "move", "getArea", "resize", "renderOnScreen"
etc...
The template version doesn't eliminate the possibility to use abstract
base classes in addition to the contracts...
This can easily be solved since they share representation:
class IGeometricalObject
{
public:
virtual void resize( double factor ) = 0;
};
class GeometricalObject
{
/*...*/
public:
void resize( double factor )
{
itsHeight *= factor;
itsWidth *= factor;
}
etc...
};
And for the members that MUST be implemented differently (I doubt they
will be many since Circle and Ellipse share implementation) we could
do a template specialization upon an inner templated class.
e.g. lets say we have a hardware accellerated area calculation routine
that works only for Circle and NOT for an Ellipse:
class IGeometricalObject
{
public:
virtual double getArea() = 0;
};
template<class Type>
class GeometricalObject : public IGeometricalObject
{
template<class TypeOfObject>
class AreaCalculator
{
public:
static double get()
{
/*...the general version, used by Ellipse...*/
}
}
template<>
class AreaCalculator<Circle>
{
public:
static double get()
{
/*...hardware accellerated version, used by Circle...*/
}
}
public:
double getArea()
{
return calculateArea<Type>::get();
}
};
Basically what I think is that the interface should expose the
commonalities that makes sence, not the functions that really doesn't
make any sense (like for instance semi_minor_axis and semi_major_axis)
[snip]
| Quote: | In my opinion, apart from the code being ill-formed, the idea is fundamentally flawed.
|
There was several bugs in the original code which Bronek Kozicki has
pointed out, I've sent a reply but it hasn't showed up yet, it may
clear out some of my thoughts.
If it doesn't show up I've "errataed" my blog
(http://blog.notus.no/thomas) to show up with the bugs fixed...
Thomas Hansen
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Sat Aug 14, 2004 4:46 am Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
Thomas Hansen <polterguy (AT) gmail (DOT) com> wrote:
[]
| Quote: | Interface is a fundamental concept in OOP. It tells you what you can do with the object. The GeometricObject<> interface lies to the user - it
exposes functions while some of them won't even compile. So, you broke a major OOP rule.
The GeometricalObject could still inherit from an abstract basclass
having functions like "move", "getArea", "resize", "renderOnScreen"
etc...
The template version doesn't eliminate the possibility to use abstract
base classes in addition to the contracts...
This can easily be solved since they share representation:
class IGeometricalObject
{
public:
virtual void resize( double factor ) = 0;
};
|
[]
| Quote: | e.g. lets say we have a hardware accellerated area calculation routine
that works only for Circle and NOT for an Ellipse:
class IGeometricalObject
{
public:
virtual double getArea() = 0;
};
template<class Type
class GeometricalObject : public IGeometricalObject
{
template
class AreaCalculator
{
public:
static double get()
{
/*...the general version, used by Ellipse...*/
}
}
template
class AreaCalculator
{
public:
static double get()
{
/*...hardware accellerated version, used by Circle...*/
}
}
public:
double getArea()
{
return calculateArea
}
};
|
Well, now it's not clear what problem you are trying to solve. The last paragraph of your original post says that that is about "... interface of a Circle and Ellipsis ..." so I thought it was about interfaces. Now you've switched to implementation leaving out the problem of Circle/Ellipsis interface relations. I think, interfaces is the real problem here, implementation is wholly immaterial.
(BTW, one can't specialize an enclosed template (AreaCalculator<>) without specializing an enclosing one (GeometricalObject<>), so the code is ill-formed).
| Quote: | Basically what I think is that the interface should expose the
commonalities that makes sence, not the functions that really doesn't
make any sense (like for instance semi_minor_axis and semi_major_axis)
|
I agree with the statement.
--
Maxim Yegorushkin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Thomas Hansen Guest
|
Posted: Mon Aug 16, 2004 10:54 am Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
"Maxim Yegorushkin" <e-maxim (AT) yandex (DOT) ru> wrote
[snip]
| Quote: | Well, now it's not clear what problem you are trying to solve. The last paragraph of your original post says that that is about "... interface
of a Circle and Ellipsis ..." so I thought it was about interfaces. Now you've switched to implementation leaving out the problem of |
Circle/Ellipsis interface relations. I think, interfaces is the real problem here, implementation is wholly immaterial.
Often for "small" objects which will come in "massive" numbers you
don't want to have the overhead of a vtable and therefor choose not
use virtual functions (Circle/Ellipsis in e.g. a game engine is a good
example) and then it makes no real sense in separating the interface
from the public (/protected) members.
So I am kind of talking about both interface and implementation.
But you are right since you can't inherit both templated classes from
an abstract interface class which expects implementations of e.g.
setWidth and setRadius since one of those will not be possible to
implement in one of the classes, vice versa for the other...
| Quote: |
(BTW, one can't specialize an enclosed template (AreaCalculator<>) without specializing an enclosing one (GeometricalObject<>), so the code is
ill-formed). |
Ok, bummer by me...
You could still specializae a global function though.
btw, is specialization prohibited only for enclosed template types or
for both enclosed template types and template member functions?
| Quote: |
Basically what I think is that the interface should expose the
commonalities that makes sence, not the functions that really doesn't
make any sense (like for instance semi_minor_axis and semi_major_axis)
I agree with the statement.
|
Thank you.
And that's the core of the problem regarding Ellipsis/Circle problems.
If you're looking for commonalities between Circle and Ellipsis you
will see that they logically divide into two unrelated classes (unless
you go further up in the hierarchy) and can't logically be derived
from eachother (interface)
But you still would like to share lot's of implementation between them
since they would logically implement almost all of their
implementation the exact same way!
And this is the problem that the "Design By Contract" solution with
the enums (try to) solve...
It makes the implementor of those classes a tool to implement those
classes with the same means and still restricting users of those
classes to NOT call functions which their "interface" don't allow!
(interface here means public member functions since you operate on
concrete classes and not abstract base classes)
But anyway, the Circle/Ellipsis problem was just like a small example
of one way to use the "magic enums" I think they're useful way beyond
only that scenario!
The whole idea is to think in terms of a compile time mechanism to
implement (kind of) Design By Contract and to also make it possible to
get fewer classes in an elsewise very deep and very wide inheritence
tree (fewer classes often means less pain learning the tree and
maintaining the tree)
So basically you can "flat" out class hierarchies in ways which
traditional OO can't do!
Thomas Hansen
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
John Sheehan Guest
|
Posted: Tue Aug 17, 2004 7:03 pm Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
It appears to me that you are combining interface and implementation.
That is usually not a good idea (TM). Even worse, you appear to be
combining the interfaces and implementations from two different
classes into one template. The template, as shown, references three
member variables:
itsWidth
itsHeight
itsRadius
The member variables are orthogonal. In other words, a Geometric
shape in this example will have either a width and height or a radius,
never all three. So, it makes no sense that this template should have
code to modify all of them.
I'm not sure what are the benefits of the approach you have outlined
here. It appears that there are other ways to accomplish what you are
trying to do. If you want to allow different classes to reuse common
implementations, you might want to look at mixin classes. At least
they respect orthogonality.
Compile-time assertions are indeed a very useful mechanism. As you
mentioned, Andrei Alexandrescu, as well as others, have given some
treatment of this topic. I'm sure that there are lots of great uses
of this idea that have not been realized yet. However, I don't think
this particular example is really one that bears fruit.
John
[email]polterguy (AT) gmail (DOT) com[/email] (Thomas Hansen) wrote in message news:<101a1348.0408152300.5d8797a (AT) posting (DOT) google.com>...
| Quote: | "Maxim Yegorushkin" <e-maxim (AT) yandex (DOT) ru> wrote
[snip]
Well, now it's not clear what problem you are trying to solve. The last paragraph of your original post says that that is about "... interface
of a Circle and Ellipsis ..." so I thought it was about interfaces. Now you've switched to implementation leaving out the problem of
Circle/Ellipsis interface relations. I think, interfaces is the real problem here, implementation is wholly immaterial.
Often for "small" objects which will come in "massive" numbers you
don't want to have the overhead of a vtable and therefor choose not
use virtual functions (Circle/Ellipsis in e.g. a game engine is a good
example) and then it makes no real sense in separating the interface
from the public (/protected) members.
So I am kind of talking about both interface and implementation.
But you are right since you can't inherit both templated classes from
an abstract interface class which expects implementations of e.g.
setWidth and setRadius since one of those will not be possible to
implement in one of the classes, vice versa for the other...
(BTW, one can't specialize an enclosed template (AreaCalculator<>) without specializing an enclosing one (GeometricalObject<>), so the code is
ill-formed).
Ok, bummer by me...
You could still specializae a global function though.
btw, is specialization prohibited only for enclosed template types or
for both enclosed template types and template member functions?
Basically what I think is that the interface should expose the
commonalities that makes sence, not the functions that really doesn't
make any sense (like for instance semi_minor_axis and semi_major_axis)
I agree with the statement.
Thank you.
And that's the core of the problem regarding Ellipsis/Circle problems.
If you're looking for commonalities between Circle and Ellipsis you
will see that they logically divide into two unrelated classes (unless
you go further up in the hierarchy) and can't logically be derived
from eachother (interface)
But you still would like to share lot's of implementation between them
since they would logically implement almost all of their
implementation the exact same way!
And this is the problem that the "Design By Contract" solution with
the enums (try to) solve...
It makes the implementor of those classes a tool to implement those
classes with the same means and still restricting users of those
classes to NOT call functions which their "interface" don't allow!
(interface here means public member functions since you operate on
concrete classes and not abstract base classes)
But anyway, the Circle/Ellipsis problem was just like a small example
of one way to use the "magic enums" I think they're useful way beyond
only that scenario!
The whole idea is to think in terms of a compile time mechanism to
implement (kind of) Design By Contract and to also make it possible to
get fewer classes in an elsewise very deep and very wide inheritence
tree (fewer classes often means less pain learning the tree and
maintaining the tree)
So basically you can "flat" out class hierarchies in ways which
traditional OO can't do!
Thomas Hansen
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Thomas Hansen Guest
|
Posted: Wed Aug 18, 2004 6:13 pm Post subject: Re: Hyperactive static_assert, or what's not there... |
|
|
[email]jsheehan (AT) gmail (DOT) com[/email] (John Sheehan) wrote in message news:<570fbf87.0408161934.11c7249f (AT) posting (DOT) google.com>...
| Quote: | It appears to me that you are combining interface and implementation.
That is usually not a good idea (TM).
|
Often you don't have a choice, like for instance if you had a polygon
class and you have a scene in a game engine with > 100K polygons and
you're creating them on the fly and wish them to have small size and
be fast to create and use,
then not only the memory overhead of a vtable but also the indirect
lookup into the vtable to call virtual functions will be more then you
can tolearete to maintain 60 frames per second...
But in general terms I agree that interface and implementation should
be separated...
But this is probably a "non interesting" aspect of the issue as I will
try to explain later...
| Quote: | Even worse, you appear to be
combining the interfaces and implementations from two different
classes into one template. The template, as shown, references three
member variables:
|
No it doesn't...
Read again...
;)
| Quote: |
itsWidth
itsHeight
itsRadius
|
It only contains two variables and both are referenced in both Circle
and Ellipsis.
But you are right, the circle class has got one redundant member!
| Quote: |
The member variables are orthogonal. In other words, a Geometric
shape in this example will have either a width and height or a radius,
never all three. So, it makes no sense that this template should have
code to modify all of them.
|
I agree!
The circle/ellipsis problem is probably not a good example of
utilizing the "Magic Enums"...
I'll try to come up with a better example later in this post!
| Quote: |
I'm not sure what are the benefits of the approach you have outlined
here. It appears that there are other ways to accomplish what you are
trying to do. If you want to allow different classes to reuse common
implementations, you might want to look at mixin classes. At least
they respect orthogonality.
Compile-time assertions are indeed a very useful mechanism. As you
mentioned, Andrei Alexandrescu, as well as others, have given some
treatment of this topic. I'm sure that there are lots of great uses
of this idea that have not been realized yet. However, I don't think
this particular example is really one that bears fruit.
[snip] |
I use this concept in SmartWin, SmartWin is a template based C++
Windows API GUI abstraction library (hosted at sourceforge for those
interested)
But anyway...
I've got this class callled WidgetWindowBase which is the base class
of all "parent widgets" or those widgets that can contain other
widgets.
A push button on a toolbar is NOT a WidgetWindowBase but the client
window of your browser is probably a WidgetWindowBase...
Anyway, there are three distinct different types of WidgetWindowBase
classes;
WidgetWindow (the general form, utilized through calling
RegisterClassEx and CreateWindowEx in Windows API)
WidgetDialog (a dialog template utilized through a dialog template and
CreateDialog (Windows API))
WidgetMDIChild (a Multi Document Interface child window, utilized
through calling CreateMDIChild (Windows API))
But apart from the creation logic and different return logics in the
WindowsProcedure they're totally the same!
So I've separated them into three subclasses;
WidgetWindow, WidgetMDIChild and WidgetDialog which all derive from
WidgetWindowBase.
Now the problem was how to make it impossible to call Dialog/MDIChild
specific functions in a WidgetWindow and at the same time maintaining
as much encapsulation as possible!
I solved it by inheriting from WidgetWindowBase (which takes a
contract template class containing "Magic Enums"), this way I could
have the createDialog, createWindow && createMDIChild functions in the
WidgetWindowBase class and at the same time guaranteeing that they
wouldn't be called with the wrong "type" of WidgetWindowBase!
This exposes the private datamembers of WidgetWindowBase to the
createxxx functions which is a must!
And at the same time KEEP those data members private (and not
protected) as is also a MUST!
Now you're probably saying that this is bad OOD/OOP or OOx...
....but!
These functions rely on data members which is in the WidgetWindowBase
class (and MUST be there since other functions in WidgetWindowBase
which are common for all classes are depending upon them) and these
data members I want to be private for WidgetWindowBase!
I could have made them protected, but this would have exposed them to
classes derived from WidgetDialog, WidgetMDIChild && WidgetWindow
which I did NOT want to do!
So basically at the end of the day I gained more encapsulation in a
way which would have been really difficult to gain with other means
and everything happens compile time without runtime overhead at all!
Example (doesn't compile):
struct DialogWidgetContract
{
enum CanCreateDialog {OhYesWeCan};
};
struct MDIChildWidgetContract
{
enum CanCreateMDIChild {OhYesWeCan};
};
struct WindowWidgetContract
{
enum CanCreateWindow {OhYesWeCan};
};
template<class Contract>
class WidgetWindowBase
{
// Must be PRIVATE!
HANDLE itsHandle;
public:
// Must be PUBLIC and must have access to itsHandle
void createDialog()
{
Contract::CanCreateDialog;
itsHandle = ::CreateDialogEx(/*...*/);
}
// Must be PUBLIC and must have access to itsHandle
void createMDIChild()
{
Contract::CanCreateMDIChild;
itsHandle = ::CreateMDIChildWindow(/*...*/);
}
// Must be PUBLIC and must have access to itsHandle
void createWindow()
{
Contract::CanCreateWindow;
::RegisterClassEx(/*...*/);
itsHandle = ::CreateWindowEx(/*...*/);
}
// Must have access to itsHandle!!
void resize( unsigned x, unsigned y, unsigned sx, unsigned sy )
{
::MoveWindow( itsHandle, x, y, sx, sy );
}
};
int main()
{
WidgetWindowBase<DialogWidgetContract> dialog;
WidgetWindowBase<MDIChildWidgetContract> MDIChild;
WidgetWindowBase<WindowWidgetContract> window;
dialog.createDialog();// OK!
//dialog.createMDIChild();// BOOM!
//dialog.createWindow();// BOOM!
//MDIChild.createDialog();// BOOM!
MDIChild.createMDIChild();// OK!
//MDIChild.createWindow();// BOOM!
//window.createDialog();// BOOM!
//window.createMDIChild();// BOOM!
window.createWindow();// OK!
}
(end of example)
The above example I don't understand how it possible could have been
solved by any other means then the "Magic Enums" way and at the same
time keeping the "encapsulation" degree that the enums give...
But I realize that the solution is SERIOUSLY controversial since it
"breaks" everything everybody knows about classes and OOP.
But I am confident that with a little bit of afterthought it may be a
tool that more C++ developers then I will embrace and put in their
toolbox for those rear occasions that you have to make custom classes
into "unions"...
After all that's all it is about!
Making a complex defined class become a "union" without loosing
typesafety...
Thomas Hansen
PS!
http//smartwin.sourceforge.net
;)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|