 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Crosbie Fitch Guest
|
Posted: Sat Jun 10, 2006 3:16 am Post subject: Propogating constness through a const member |
|
|
Below is a compilable (on MS VS 2003) bit of code with a wee little problem.
It does not compile on Comeau because that only permits an anonymous union
to contain a single const member (all const members must be initialised,
only one member of a union can be initialised). Let's ignore that little
problemette.
Somehow I need a different member function of Item being called depending
upon whether it has been accessed via a const or non-const containing
object. Compile time detection would be preferable, but run-time is also
acceptable.
Is there some highly peculiar template malarky that can propogate the
correct constness (without increasing overhead)?
Whoever solves this has effectively enabled zero-overhead properties to
enter the C++ language (payload incurring properties are already doable).
This problem may also be solved by finding some other way to prevent the
default assign-copy operator from being called.
#include <iostream>
struct Data
{ int data[2];
void Init(int a=0,int b=0) { data[0]=a; data[1]=b; }
static Data New(int a=0,int b=0) { Data d; d.Init(a,b); return d; }
};
template <int ITEM>
class Item: private Data
{ Item& Assign(int i) { data[ITEM]=i; return *this; }
public:
operator int() const { return data[ITEM]; }
const Item& operator=(int i) const { const_cast<Item*>(this)->Assign(i);
return *this; }
};
class Twin
{
public:
union
{ Data twin;
const Item<0> d0; // const to prevent default assign-copy being called
const Item<1> d1; // const to prevent default assign-copy being called
};
Twin():twin(Data::New()) { }
Twin(int a,int b):twin(Data::New(a,b)) { }
Twin& operator=(const Twin& t) { twin=t.twin; return *this; }
};
void right(Twin& t)
{ t.d0=0; // Should be permitted given t is non-const
t.d1=0;
}
void wrong(const Twin& t)
{ t.d0=0; // Should NOT be permitted given t is const
t.d1=0; // Can it be solved without adding overhead to Twin?
}
int main()
{ Twin u(123,456),v(7, ;
printf("u.d0=%d, u.d1=%d\n",(int)u.d0,(int)u.d1);
u.d0=v.d0; // u.d1 should be left unaffected
printf("u.d0=%d, u.d1=%d\n",(int)u.d0,(int)u.d1);
wrong(u);
printf("u.d0=%d, u.d1=%d\n",(int)u.d0,(int)u.d1);
printf("sizeof(Twin)=%d, sizeof(Data)=%d\n",sizeof(Twin),sizeof(Data));
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 |
|
 |
Greg Herlihy Guest
|
Posted: Sun Jun 11, 2006 12:35 am Post subject: Re: Propogating constness through a const member |
|
|
Crosbie Fitch wrote:
| Quote: | Below is a compilable (on MS VS 2003) bit of code with a wee little problem.
It does not compile on Comeau because that only permits an anonymous union
to contain a single const member (all const members must be initialised,
only one member of a union can be initialised). Let's ignore that little
problemette.
Somehow I need a different member function of Item being called depending
upon whether it has been accessed via a const or non-const containing
object. Compile time detection would be preferable, but run-time is also
acceptable.
Is there some highly peculiar template malarky that can propogate the
correct constness (without increasing overhead)?
Whoever solves this has effectively enabled zero-overhead properties to
enter the C++ language (payload incurring properties are already doable).
This problem may also be solved by finding some other way to prevent the
default assign-copy operator from being called.
#include <iostream
struct Data
{ int data[2];
void Init(int a=0,int b=0) { data[0]=a; data[1]=b; }
static Data New(int a=0,int b=0) { Data d; d.Init(a,b); return d; }
};
template <int ITEM
class Item: private Data
{ Item& Assign(int i) { data[ITEM]=i; return *this; }
public:
operator int() const { return data[ITEM]; }
const Item& operator=(int i) const { const_cast<Item*>(this)->Assign(i);
return *this; }
};
|
Declaring the assignment operator a const method of the class template
"Item" makes the undesirable behavior (that is, assigning a value to a
const Item object) legal. So removing the "const" from the declaration
of operator=() will solve the problem - because assignment then will be
a legal operation for non-const Item objects only.
Greg
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Crosbie Fitch Guest
|
Posted: Mon Jun 12, 2006 2:29 am Post subject: Re: Propogating constness through a const member |
|
|
"Greg Herlihy" <greghe (AT) pacbell (DOT) net> wrote in message
news:1149899194.781500.143770 (AT) i40g2000cwc (DOT) googlegroups.com...
| Quote: | Crosbie Fitch wrote:
const Item& operator=(int i) const { const_cast<Item*>(this)->Assign(i);
Declaring the assignment operator a const method of the class template
"Item" makes the undesirable behavior (that is, assigning a value to a
const Item object) legal. So removing the "const" from the declaration
of operator=() will solve the problem - because assignment then will be
a legal operation for non-const Item objects only.
|
Unfortunately, that then prevents the assignment u.d0=v.d0, because u.d0 is
const and only non-const assignment operators are available (=(int) and
default assign-copy.)
If you make u.d0 non-const then the default assign-copy will be selected
before =(int), and the default assign copy cannot be overridden and performs
undesired behaviour.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
johnchx2@yahoo.com Guest
|
Posted: Wed Jun 14, 2006 3:23 am Post subject: Re: Propogating constness through a const member |
|
|
Crosbie Fitch wrote:
| Quote: | Whoever solves this has effectively enabled zero-overhead properties to
enter the C++ language (payload incurring properties are already doable).
|
After reading the code that you posted on comp.std.c++, I finally
figured out that this was what you were after. Basically, you're
trying to use an anonymous union to achieve two things:
(1) emulate multiple virtual inheritance without the ususal space
overhead and
(2) bind names to different "typed views" of the same object (which
would be different base classes, if multiple inheritance were used
instead of a union) without the space overhead of reference members
As you know, reading and writing through different members of a union
yields undefined behavior. To my mind, this renders unions an entirely
unsuitable tool for implementing properties. Others' opinions may
differ.
The good news is that #1, above, is achievable by different means. #2,
the naming problem, is harder. As far as I can tell, the best that can
be achieved in the current language (without space overhead) requires
property names to be used with function call syntax. In other words:
T t, u;
t.some_property() = u.some_property();
t.other_property() = 5;
rather than the preferred
T t, u;
t.some_property = u.some_property;
t.other_property = 5;
To get to the preferred syntax with no space overhead, we'd need to add
a feature to the language: zero size member references. (These would
be member references bound -- perhaps by in-class-body initializers --
to subobjects, including base class subobjects, of the containing
object.)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Greg Herlihy Guest
|
Posted: Wed Jun 14, 2006 3:28 am Post subject: Re: Propogating constness through a const member |
|
|
Crosbie Fitch wrote:
| Quote: | "Greg Herlihy" <greghe (AT) pacbell (DOT) net> wrote in message
news:1149899194.781500.143770 (AT) i40g2000cwc (DOT) googlegroups.com...
Crosbie Fitch wrote:
const Item& operator=(int i) const { const_cast<Item*>(this)->Assign(i);
Declaring the assignment operator a const method of the class template
"Item" makes the undesirable behavior (that is, assigning a value to a
const Item object) legal. So removing the "const" from the declaration
of operator=() will solve the problem - because assignment then will be
a legal operation for non-const Item objects only.
Unfortunately, that then prevents the assignment u.d0=v.d0, because u.d0 is
const and only non-const assignment operators are available (=(int) and
default assign-copy.)
If you make u.d0 non-const then the default assign-copy will be selected
before =(int), and the default assign copy cannot be overridden and performs
undesired behaviour.
|
A const assignment operator is a contradiction in terms - a const
object is const precisely because it cannot be assigned a new value.
Therefore, any design that leads to the declaration of a const
assignment operator is a design in serious need of revision.
The problem with the interface above is that it is essentially
implemented in C. Unfortunately, C is not really up to the task of
providing good class interfaces - a fact which no doubt inspired C++.
In other words, programs written C use instance variables and unions
only because the language offers nothing better. But C++ does offer
something better, namely, class methods. Generally, when devising any
kind of interface, it is always a good idea to favor methods over
instance variables. Class methods allow a program to mediate access to
the object's data - that is, methods allow the program to establish
precondtions, post-conditions and invariants.
The natural improvement to make to the current interface is therefore
simply to declare d0 and d1 as class methods instead of as instance
variables. The only change needed in existing client code would be to
add a pair of parentheses wherever d0 and d1 appear in the code - no
other change would be required. For example, instead of writing:
u.d0 = v.d0;
the statement in C++ would be written like this:
u.d0() = v.d0();
This small change in syntax though makes a significant improvement to
the implementation - by eliminating the union nonsense, the clunky
instance variables and the entire business of const objects that are
not really const at all. The C++ interface that does all of this would
look something like this:
class Twin
{
Data twin;
public:
...
int& d0() { return twin.data[0]; }
int d0() const { return twin.data[0]; }
int& d1() { return twin.data[1]; }
int d1() const { return twin.data[1]; }
};
Greg
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Crosbie Fitch Guest
|
Posted: Thu Jun 15, 2006 3:27 am Post subject: Re: Propogating constness through a const member |
|
|
<johnchx2 (AT) yahoo (DOT) com> wrote in message
news:1150129660.805687.92620 (AT) i40g2000cwc (DOT) googlegroups.com...
| Quote: | As you know, reading and writing through different members of a union
yields undefined behavior.
|
However, if the members' data within the union isn't read or written in the
process of calling methods on those members, then there's no undefined
behaviour. Only the address of those members is actually used.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Crosbie Fitch Guest
|
Posted: Thu Jun 15, 2006 3:28 am Post subject: Re: Propogating constness through a const member |
|
|
"Greg Herlihy" <greghe (AT) pacbell (DOT) net> wrote in message
news:1150148151.809620.157190 (AT) c74g2000cwc (DOT) googlegroups.com...
| Quote: | A const assignment operator is a contradiction in terms - a const
object is const precisely because it cannot be assigned a new value.
Therefore, any design that leads to the declaration of a const
assignment operator is a design in serious need of revision.
|
Any feature introduced into the C++ language that was grievously hobbled by
permitting member objects with all assignment operators apart from the
assign-copy operator (which can only obviously be prevented from selection
by making it const) is in serious need of revision.
| Quote: | u.d0() = v.d0();
This small change in syntax though makes a significant improvement to
the implementation - by eliminating the union nonsense, the clunky
instance variables and the entire business of const objects that are
not really const at all.
|
Changing the problem into a different problem with a well known solution is
not solving the problem.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Crosbie Fitch Guest
|
Posted: Fri Jun 16, 2006 1:38 am Post subject: Re: Propogating constness through a const member |
|
|
<johnchx2 (AT) yahoo (DOT) com> wrote in message
news:1150327119.822085.267500 (AT) r2g2000cwb (DOT) googlegroups.com...
| Quote: | struct A { void do_a() {} };
struct B { void do_b() {} };
union AB { A a; B b; };
int main()
{
AB ab;
ab.b = B();
ab.a.do_a(); // line 1
B b;
A& a = reinterpret_cast<A&>(b);
a.do_a(); // line 2
}
I believe that line 1 and line 2 both have undefined behavior, for the
same reason: 9.3.1/1 says, "(i)f a nonstatic member function of a
class X is called for an object that is not of type X, or of a type
derived from X, the behavior is undefined."
Both object expressions ("ab.a" and "a") denote objects that have type
B, not type A, so using them to call an A member function results in
undefined behavior.
|
Well, in my case it's more like this:
struct X { float f,g; };
struct A:X { void do_a() {} };
struct B:X { void do_b() {} };
struct XAB {
union { X x; A a; B b; };
};
XAB xab;
xab.x=X();
xab.a.do_a(); // line 3
But, according to the clause you cite, as x is not an A or derived from A,
the behaviour is also undefined.
So, do_a() doesn't even get a chance to static_cast<X*>(this) then?
(I will assume that there is no difference here between named and anonymous
unions.)
However, given that A,B and X must all be POD objects, and so can't actually
have the hypothetically perverse representations the standard has bent over
backwards to cater for, I'd contend that this PECULIAR situation was one in
which, if the behaviour was ever unexpected (demonstrably undefined), one
should worry about the compiler.
Anyway....
struct X { float f,g; };
struct A: virtual X { void do_a() {} };
struct B: virtual X { void do_b() {} };
struct M: A,B { };
struct XAB {
union { M m; A a; B b; };
};
Would that make you happier? (assuming it's actually valid, i.e. union
members can virtual/multi inherit)
a is now an object derived from A (given m initialised).
[ 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
|
|