Topic: Relaxing const construction constraints
Author: google.com@kevin.saff.net (K Saff)
Date: Mon, 12 Jan 2004 20:19:38 +0000 (UTC) Raw View
Currently, the presence of a pure virtual function dictates that no
object can be constructed from the class. However, I often see a need
for immutable objects that should only support the constant part of an
interface. In these classes I find myself inserting stub functions
for the pure virtual, non-const functions in the interface that should
never be called, since the objects are const constructed anyway.
The frequently advertised way to circumvent this problem is to
abstract out the constant part out of the interface, but I find this
can quickly introduce unwieldly class heirarchies, particularly when
the constant class needs to support multiple interfaces. In addition,
it forces the base abstract class to decide whether to support a
feature (immutable objects) that would best be decided by derived
classes.
And yet, it would seem that by relaxing the constraints on
construction of "abstract" classes an ideal solution can be found.
Allow a constant object to be (explicitly) created as long as there
are no const pure virtual functions, and no pure virtual destructor.
This permits defining a class that can be used only for const objects,
which the C++ language currently lacks. This eliminates the need for
stub non-const functions on const objects, and lessens the need to
abstract out the const part of an interface.
This would cause the following standard changes:
1) A class is abstract only if it contains a const pure virtual
function, or a pure virtual destructor.
2) A class with pure virtual functions may be (explicitly) constructed
in a _const_ object (either const Foo or new const Foo) if it is not
abstract as above.
This language change involves a simple relaxation on the
initialization of objects. This does not introduce any new keywords
or new syntax, nor even recycle existing keywords. It will not break
any existing code. It is quite likely that the only compiler change
needed would be an additional compile-time lookup on the construction
of const objects. In addition, it seems intuitive that we should
allow the construction of objects any time all the functions which may
be invoked on that object (without casts) are callable.
Possible objections I have considered:
A) Existing code may rely on const_cast on an abstract base class
pointer, and call a pure virtual function, invoking undefined
behavior. However, as the proposal affects only objects constructed
const, something a user cannot be prevented from doing with a derived
class, this code can already invoke undefined behavior.
B) Some formerly abstract classes may now be (const) instantiated.
This is of course the point of the proposal, but why disrespect the
class author's purpose that the class never be instantiated? I
believe that if a class has no const pure virtuals, this indicates
that there is some "reasonable" default behavior for a constant object
of that class. Since some reasonable behavior exists, why force the
user to derive a class to get at that behavior?
I think that one could even allow construction by implicit conversion
under this scheme, since an implicit conversion should result in
temporary that is bound to a const&. If there is a problem with this,
then one would need to specify that these constructors can only be
called explicitly.
I could not find any discussion of this idea in the archives, although
I do not know what it should be called. Has a similar proposal arisen
before? Is there any reason not to incorporate this?
--
KCS
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: technews@kangaroologic.com ("Jonathan Turkanis")
Date: Mon, 12 Jan 2004 20:48:36 +0000 (UTC) Raw View
"K Saff" <google.com@kevin.saff.net> wrote in message
news:b86b47e.0401121038.5a80e68e@posting.google.com...
> Currently, the presence of a pure virtual function dictates that no
> object can be constructed from the class. However, I often see a
need
> for immutable objects that should only support the constant part of
an
> interface. In these classes I find myself inserting stub functions
> for the pure virtual, non-const functions in the interface that
should
> never be called, since the objects are const constructed anyway.
>
> The frequently advertised way to circumvent this problem is to
> abstract out the constant part out of the interface, but I find this
> can quickly introduce unwieldly class heirarchies, particularly when
> the constant class needs to support multiple interfaces. In
addition,
> it forces the base abstract class to decide whether to support a
> feature (immutable objects) that would best be decided by derived
> classes.
>
> And yet, it would seem that by relaxing the constraints on
> construction of "abstract" classes an ideal solution can be found.
> Allow a constant object to be (explicitly) created as long as there
> are no const pure virtual functions, and no pure virtual destructor.
> This permits defining a class that can be used only for const
objects,
> which the C++ language currently lacks. This eliminates the need
for
> stub non-const functions on const objects, and lessens the need to
> abstract out the const part of an interface.
>
What about this?
struct AbstractDog {
virtual ~AbstractDog();
virtual void bark() = 0;
};
int main()
{
const AbstractDog a; // Okay?
AbstractDog b(a);
b.bark(); // Ouch!
}
Or do I misunderstand your proposal?
Jonathan
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: technews@kangaroologic.com ("Jonathan Turkanis")
Date: Mon, 12 Jan 2004 22:20:36 +0000 (UTC) Raw View
""Jonathan Turkanis"" <technews@kangaroologic.com> wrote in message
news:btv0os$bhr6n$1@ID-216073.news.uni-berlin.de...
>
> What about this?
>
> struct AbstractDog {
> virtual ~AbstractDog();
> virtual void bark() = 0;
> };
>
> int main()
> {
> const AbstractDog a; // Okay?
> AbstractDog b(a);
> b.bark(); // Ouch!
> }
>
> Or do I misunderstand your proposal?
>
> Jonathan
>
Sorry, that was way off.
Jonathan
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: rogero@howzatt.demon.co.uk ("Roger Orr")
Date: Mon, 12 Jan 2004 22:20:41 +0000 (UTC) Raw View
""Jonathan Turkanis"" <technews@kangaroologic.com> wrote in message
news:btv0os$bhr6n$1@ID-216073.news.uni-berlin.de...
>
> What about this?
>
> struct AbstractDog {
> virtual ~AbstractDog();
> virtual void bark() = 0;
> };
>
> int main()
> {
> const AbstractDog a; // Okay?
> AbstractDog b(a);
> b.bark(); // Ouch!
> }
This would presumably still fail to compile under the proposal since 'b' is
not const.
I think the proposal sounds interesting - my question is whether it would be
generally useful enough to support.
I haven't myself found much need for "immutable objects only supporting the
constant part of an interface" - but that may simply reflect the lack of
this facility.
Roger Orr
--
MVP in C++ at www.brainbench.com
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: rlankine@hotmail.com ("Risto Lankinen")
Date: Tue, 13 Jan 2004 19:56:39 +0000 (UTC) Raw View
Hi!
"K Saff" <google.com@kevin.saff.net> wrote in message
news:b86b47e.0401121038.5a80e68e@posting.google.com...
[snip]
> Allow a constant object to be (explicitly) created as long as there
> are no const pure virtual functions, and no pure virtual destructor.
[snip]
> This would cause the following standard changes:
3) Constructors with a "const" specifier should become a legal
and supported feature, or else the pure virtual (yet non-const)
methods could be accidentally called if the constructor passes
its "this" pointer to an outside entity (because currently the type
of an object inside its own constructor is always non-const).
[snip]
>Possible objections I have considered:
C) Addition of const constructors would change the overload
and ambiguity resolution mechanism.
Otherwise this is a very interesting proposal.
Cheers!
- Risto -
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: lgalfaso@fd.com.ar (=?iso-8859-1?Q?Lucas_Galfas=F3?=)
Date: Wed, 14 Jan 2004 19:43:40 +0000 (UTC) Raw View
It sounds interesting, but how would you handle const_cast?
ie
struct constClass
{
virtual void messThings () = 0;
void maybeMess () const { const_cast<constClass*>(this) ->messThings(); }
}
int main ()
{
const constClass foo;
foo.maybeMess ();
}
L/
---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.555 / Virus Database: 347 - Release Date: 12/23/2003
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: USENET.Ken@Alverson.net (Ken Alverson)
Date: Thu, 15 Jan 2004 03:13:09 +0000 (UTC) Raw View
"Lucas Galfas=F3" <lgalfaso@fd.com.ar> wrote in message
news:000501c3dad5$b4a1aed0$f37bfea9@lucasxp1...
> It sounds interesting, but how would you handle const_cast?
const_casting something that wasn't originally non-const invokes undefine=
d
behavior. Non-issue.
Ken
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: a9804814@unet.univie.ac.at (Thomas Mang)
Date: Thu, 15 Jan 2004 03:13:49 +0000 (UTC) Raw View
Lucas Galfas=F3 schrieb:
> It sounds interesting, but how would you handle const_cast?
> ie
>
> struct constClass
> {
> virtual void messThings () =3D 0;
> void maybeMess () const { const_cast<constClass*>(this) ->messThings(=
); }
> }
>
> int main ()
> {
> const constClass foo;
> foo.maybeMess ();
> }
const_cast on an actually const object yields undefined behavior, so ther=
e is
hardly a problem :-)
regards,
Thomas
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 15 Jan 2004 03:14:39 +0000 (UTC) Raw View
lgalfaso@fd.com.ar (Lucas Galfas ) wrote in message news:<000501c3dad5$b4a1aed0$f37bfea9@lucasxp1>...
> It sounds interesting, but how would you handle const_cast?
> ie
>
> struct constClass
> {
> virtual void messThings () = 0;
> void maybeMess () const { const_cast<constClass*>(this) ->messThings(); }
> }
>
> int main ()
> {
> const constClass foo;
> foo.maybeMess ();
> }
>
>
> L/
Probably the same way const_cast is handled here:
const char* str = "Hello, world";
const_cast<char*>(str)[3] = 'a';
In other words, undefined behavior. Seems reasonable to me.
It wouldn't be the first occurrence of undefined behavior with pure
virtual functions:
class Foo {
public:
virtual void F() = 0;
};
class Bar : public Foo {
virtual void F();
};
Bar b;
b.Foo::F();
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: rlankine@hotmail.com ("Risto Lankinen")
Date: Thu, 15 Jan 2004 05:55:11 +0000 (UTC) Raw View
""Kevin Saff"" <google.com@kevin.saff.net> wrote in message
news:HrG9pI.MIu@news.boeing.com...
> ""Risto Lankinen"" <rlankine@hotmail.com> wrote in message
> news:zONMb.6481$k4.144619@news1.nokia.com...
> >
> > 3) Constructors with a "const" specifier should become a legal
> > and supported feature, or else the pure virtual (yet non-const)
> > methods could be accidentally called if the constructor passes
> > its "this" pointer to an outside entity (because currently the type
> > of an object inside its own constructor is always non-const).
>
> For present discussion purposes I'd like to call a class with:
> 1) no pure virtual const functions
> 2) no pure virtual destructor
> a "const class", to indicate it can only be constructed in a const object.
>
> Now that's out of the way... I don't think "const constructors" are
> necessary, since directly or indirectly calling pure virtuals from
abstract
> ctors/dtors already invokes undefined behavior (10.4.6). The difference
is
> a "const class" can now be directly constructed, where it could not be
> before. Perhaps it would be necessary to make a minor change to the
> language of 10.4.6 to indicate that one must be wary of pure virtual calls
> in both abstract and const class ctors/dtors.
The real difference is that the current standard requires a class
with pure virtuals to be further inherited so that the pure virtual
methods eventually get implemented by some derived class down
the line. The window of opportunity for calling a pure virtual
method is restricted to the time (and place!) of the constructor
of the class that has the pure virtuals. Allowing such classes to
become objects on their own extends this window thru the entire
life time of the instance, and the place thru the of the entire
Here's what I mean:
class C *p = 0;
class C
{
public:
C() { p = this; }
virtual void boom() = 0;
};
int main()
{
const C c;
if( p ) p->boom();
}
This would compile if the "const class" scheme were allowed.
Currently it won't compile as shown. Class 'C' can be derived,
and the derived class must implement the 'void boom()' making
the construct safe.
With const constructors the "const class" scheme could be made
safe:
class C
{
public:
C() { p = this; }
C() const { p = 0; } // Hypothetical const constructor
virtual void boom() = 0;
};
But hey, there's another pitfall: A "const class" instance could
not be created using "new" unless an operator const new() were
added as well:
const C *p = new C(); // Should this work, or...
const C *p = new const C(); // ... should this syntax be added?
Cheers!
- Risto -
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: m@remove.this.part.rtij.nl (Martijn Lievaart)
Date: Thu, 15 Jan 2004 17:03:42 +0000 (UTC) Raw View
On Thu, 15 Jan 2004 05:55:11 +0000, Risto Lankinen wrote:
> Here's what I mean:
>
> class C *p = 0;
>
> class C
> {
> public:
> C() { p = this; }
> virtual void boom() = 0;
> };
>
> int main()
> {
> const C c;
>
> if( p ) p->boom();
> }
>
> This would compile if the "const class" scheme were allowed.
I don't see this as a problem. You deliberately circumvent the constness
of the object, anything what happens from there on is your own problem.
> Currently it won't compile as shown. Class 'C' can be derived,
> and the derived class must implement the 'void boom()' making
> the construct safe.
That does not make it safe, you still call a non const member on a const
object. So there is no problem introduced by this proposal.
> But hey, there's another pitfall: A "const class" instance could
> not be created using "new" unless an operator const new() were
> added as well:
>
> const C *p = new C(); // Should this work, or...
> const C *p = new const C(); // ... should this syntax be added?
This is a good line of thinking. Conceptualy, if I want to new a const
object, this should be allowed. But upon checking the standard, I think
this is already allowed? See f.i. 5.3.4.15.
HTH,
M4
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: rlankine@hotmail.com ("Risto Lankinen")
Date: Fri, 16 Jan 2004 07:49:55 +0000 (UTC) Raw View
Hi!
""Kevin Saff"" <google.com@kevin.saff.net> wrote in message
news:HrJF2L.93G@news.boeing.com...
> ""Risto Lankinen"" <rlankine@hotmail.com> wrote in message
> news:FH5Nb.6575$k4.146737@news1.nokia.com...
> >
> > Here's what I mean:
> >
> > class C *p = 0;
> >
> > class C
> > {
> > public:
> > C() { p = this; }
> > virtual void boom() = 0;
>
> virtual ~C() {} /*obPedantry*/
>
> > };
> >
> > int main()
> > {
> > const C c;
> >
> > if( p ) p->boom();
> > }
>
>
> > This would compile if the "const class" scheme were allowed.
> > Currently it won't compile as shown. Class 'C' can be derived,
> > and the derived class must implement the 'void boom()' making
> > the construct safe.
>
> Yes, I didn't think of that loophole at first. Currently, though C must
be
> derived, how likely is it to make the construct safe?
I would argue that "const constructors" is somewhat needed
feature anyway to plug the hole of the const-security in the
C++ type system. It would merely become strictly needed if
the "const class" scheme were implemented.
> It seems the example class C (and I imagine most that could produce this
> behavior) are already pathological enough that users must be careful to
> avoid possible undefined behavior - such undefined behvior will _always_
> occur if D : C defines any of its non-const functions to actually modify
> itself, and is instantiated const:
I'm not sure what you mean by "pathological", but there is a
common idiom where a base class (often implemented as a
mixin) registers all instances into an array for some overseeing
entity to perform global operations to all instances.
Think of a Window and WindowManager for instance, where
all Window objects register themselves to the WindowManager
singleton. The WindowManager has a method Tile() which
manipulates all registered Window objects. When someone
creates a const instance of any derivant of Window, it still ends
up in the Window array, and any subsequent Tile() invocation
will modify a const object.
All this can silently happen in a totally unrelated compilation unit.
Without "const constructors" the Window and WindowManager
author can neither prevent the problem, nor communicate (using
the source code) to the user that const instances are illegal.
> > const C *p = new C(); // Should this work, or...
> > const C *p = new const C(); // ... should this syntax be added?
>
> Actually I think this is not new syntax, and is already covered - check
out
> this example in 7.1.5.1/5:
Indeed it is. Thanks for the correction!
- Risto -
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: ahp6@email.byu.edu ("Adam H. Peterson")
Date: Fri, 16 Jan 2004 23:20:12 +0000 (UTC) Raw View
>>Allow a constant object to be (explicitly) created as long as there
>>are no const pure virtual functions, and no pure virtual destructor.
>
>
> [snip]
>
>
>>This would cause the following standard changes:
>
>
> 3) Constructors with a "const" specifier should become a legal
> and supported feature, or else the pure virtual (yet non-const)
> methods could be accidentally called if the constructor passes
> its "this" pointer to an outside entity (because currently the type
> of an object inside its own constructor is always non-const).
That would cause a problem anyway.
struct Base;
void someone_else(Base &that);
struct Base {
Base()
{
someone_else(*this);
}
virtual void pure() =0;
};
void someone_else(Base &that) {
that.pure();
}
struct Derived : Base {
virtual void pure() {
}
};
int main() {
// Right here, bad things happen.
// Base's constructor calls someone_else()
// before derived becomes a Derived.
// Then someone_else() calls Base::pure().
Derived derived;
}
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: ahp6@email.byu.edu ("Adam H. Peterson")
Date: Fri, 16 Jan 2004 23:20:49 +0000 (UTC) Raw View
> It wouldn't be the first occurrence of undefined behavior with pure
> virtual functions:
>
> class Foo {
> public:
> virtual void F() = 0;
> };
>
> class Bar : public Foo {
> virtual void F();
> };
>
> Bar b;
>
> b.Foo::F();
Even simpler:
class Foo {
public:
Foo() { F(); }
virtual void F() = 0;
};
class Bar : public Foo {
virtual void F() {}
}
Bar b;
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: rlankine@hotmail.com ("Risto Lankinen")
Date: Fri, 16 Jan 2004 23:20:59 +0000 (UTC) Raw View
"Martijn Lievaart" <m@remove.this.part.rtij.nl> wrote in message
news:pan.2004.01.15.12.39.44.214834@remove.this.part.rtij.nl...
> On Thu, 15 Jan 2004 05:55:11 +0000, Risto Lankinen wrote:
> > Here's what I mean:
> >
> > class C *p = 0;
> >
> > class C
> > {
> > public:
> > C() { p = this; }
> > virtual void boom() = 0;
> > };
> >
> > int main()
> > {
> > const C c;
> >
> > if( p ) p->boom();
> > }
> >
> > This would compile if the "const class" scheme were allowed.
>
> I don't see this as a problem. You deliberately circumvent the constness
> of the object, anything what happens from there on is your own problem.
No circumvention whatsoever, deliberate or otherwise!
The constructor leaks a non-const pointer to an object
that is *eventually* going to be a const.
It seems deliberate in the above code, but only because
the example is very terse to illustrate the problem. In real
world it could happen in an other compilation unit (and
not main() ) as easily.
Cheers!
- Risto -
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: a9804814@unet.univie.ac.at (Thomas Mang)
Date: Sun, 18 Jan 2004 18:14:00 +0000 (UTC) Raw View
Kevin Saff schrieb:
>
> Please tell me what application a const Window would have? :) Modifying an
> object declared _const_ already has undefined behavior
That's not true as it stands.
The const object could well have mutable members, and it's state can thus alter.
regards,
Thomas
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: m@remove.this.part.rtij.nl (Martijn Lievaart)
Date: Sun, 18 Jan 2004 18:14:01 +0000 (UTC) Raw View
On Fri, 16 Jan 2004 23:20:59 +0000, Risto Lankinen wrote:
>> > This would compile if the "const class" scheme were allowed.
>>
>> I don't see this as a problem. You deliberately circumvent the constness
>> of the object, anything what happens from there on is your own problem.
>
> No circumvention whatsoever, deliberate or otherwise!
> The constructor leaks a non-const pointer to an object
> that is *eventually* going to be a const.
>
> It seems deliberate in the above code, but only because
> the example is very terse to illustrate the problem. In real
> world it could happen in an other compilation unit (and
> not main() ) as easily.
Yes, but my main point still stands. You circumvent the constness of the
object. It already is UB (you call a non-const member on a const object),
so I still don't see it as a problem with regard to this proposal.
I also agree that your code will not compile today, but this code:
class C *p = 0;
class C
{
public:
C() { p = this; }
virtual void boom() = 0;
};
class D : public C
{
virtual void boom() {}
};
int main()
{
const D d;
if( p ) p->boom();
}
This shows the same problem in code that is compilable under the current
rules.
Regards,
M4
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]