should this compile? [const]

M

Mark P

The following compiles without error on four different platforms (Linux
g++, Sun CC, HP aCC, Win Dev-C++) so I suspect it's ok, but I don't see
why this isn't a const-related error. pB is a pointer to constant B and
foo() is a non-constant function invoked on a (reference) member of B.
Can someone explain this?

Thanks,
Mark

struct A
{
void foo () {}
};

struct B
{
B () : a(*new A) {}
A& a;
};

struct C
{
C () : pB(new B) {}
void bar () {pB->a.foo();}

const B* pB;
};

int main ()
{
C c;
c.bar();
}
 
V

Victor Bazarov

Mark said:
The following compiles without error on four different platforms
(Linux g++, Sun CC, HP aCC, Win Dev-C++) so I suspect it's ok, but I
don't see why this isn't a const-related error. pB is a pointer to
constant B and foo() is a non-constant function invoked on a
(reference) member of B. Can someone explain this?

Thanks,
Mark

struct A
{
void foo () {}
};

OK, 'foo' is a non-const member, no mystery here. I will not be
called for an object of type 'const A'. But it can be called for
any object of type 'A'.
struct B
{
B () : a(*new A) {}
A& a;

'a' is a _reference_ to a _non-const_ object. IOW, 'a' refers to
an object of type 'A', not to an object of type 'const A'. The
const-ness of the object that contains this reference does not
matter here. Even if 'B' here is a 'const B', 'a' will still
refer to a non-const 'A'.
};

struct C
{
C () : pB(new B) {}
void bar () {pB->a.foo();}

const B* pB;

pB here points to a 'const B'. OK. When you try to access any
member of '*pB' object, the member is going to be 'const', but
that's only the member _itself_. In the case of 'a', what's
const here is the reference, which is irrelevant really because
references themselves cannot be changed. The _referred_ object,
however, is not 'const', although technically 'a' is. What you
actually have (if it were possible in C++) is:

A & const a;

(a constant reference that refers to a non-constant object).

It would be a bit clearer if you had 'a' of type 'A*'. If the
'B' in which 'a' lived would be const, then the type of its 'a'
would be {A* const}, and not {A const *}, which would still allow
changing the pointed object (and of course call a non-const 'foo'
member).
};

int main ()
{
C c;
c.bar();
}

V
 
A

AnalogFile

Mark said:
The following compiles without error on four different platforms (Linux
g++, Sun CC, HP aCC, Win Dev-C++) so I suspect it's ok, but I don't see
why this isn't a const-related error. pB is a pointer to constant B and
foo() is a non-constant function invoked on a (reference) member of B.
Can someone explain this?

B is constant. The A it references is not.

Make the B member a pointer to A instead of a reference for a very
similar situation that will be easier to understand.

The pointer (member of B) would be const. But the A it points to would not.

The fact it's a reference does not change this. Except
that you would not be able to modify the B member EVEN IF the B was not
constant (because you cannot rebind references).

In other words: a reference member of a const object is a constant
reference, not a reference to a constant something. And this is true
even if the language would not let you modify a non costant reference to
either constant or non constant whatever.

HTH
 
M

Mark P

AnalogFile said:
B is constant. The A it references is not.

Make the B member a pointer to A instead of a reference for a very
similar situation that will be easier to understand.

The pointer (member of B) would be const. But the A it points to would not.

The fact it's a reference does not change this. Except
that you would not be able to modify the B member EVEN IF the B was not
constant (because you cannot rebind references).

In other words: a reference member of a const object is a constant
reference, not a reference to a constant something. And this is true
even if the language would not let you modify a non costant reference to
either constant or non constant whatever.

HTH

Thanks to both you and Victor. That clears it up very well.
 
A

AnalogFile

Mark said:
....

Thanks to both you and Victor. That clears it up very well.

One more thing (with apologies to Steve Jobs),

Note: what follows is probably basic on comp.lang.c++, but the thread is
crossposted with alt.comp.lang.learn.c-c++ and I'm leaving the former
in, for now, for thread completeness. If this will spawn a longer
subthread we may want to break out and stop crossposting.


I've been thinking why you would want the A to be constant too and if
this made sense, and how you would actually do it if you need A to be
constant when seen throu a constant B.

Consider:

class A {
public:
virtual void mutate();
virtual ~A()=0;
static A* New( /* some params */ );
protected:
A();
A(const A&);
A& operator=(const A&);
};

The above is somewhat "classic". You probably recognize some patterns.

A is the (abstract) base of a hidden hierarchy. You need not know the
actual implementation classes: you instantiate only through A::New. And
that gives you a dynamically allocated instance of some class derived
from A.

Now for B:

// we have an A, and it's "value" is part of our "value"
class B {
public:
A a; // error
};

There are many possible reasons to have an A bound to a B. Here a B "has
an" A as part of its 'value' (or 'state' or whatever). But it does not
work because we can only have dynamic As. Therefore we go with something
like you did in your OP:

class B {
public:

A &a;

B()
:a( * A::New(/*params*/) )
{
}

~B() { delete &a; }

};

I hope the reason you used the *ugly* initializer '*new A' is not this
one but just a way to represent your code without a lot of details and
you actually have the reference initialized from a parameter or
something. But I'll do the same: I'm just making a completely different
point and the above code will go away in a moment.

Now the above works. Or does it?
We have the exact problem you asked about:

void foo( const B& b )
{
// this violates contract
b.a.mutate();
}

As the A is part of the "value" of B, you are changing the "value" of a
supposedly constant object.

How do we solve this?
That is: how do you make sure the A is also seen as constant if the B is
such?

This is were function overloading comes to the game. And the reason
accessors are much superior to exposed data members:

class B {
A *pa;
public:
B()
:pa( A::New(/*params*/) )
{
}

~B() { delete &a; }

A& a() { return *pa; }
const A& a() const { return *pa; }
};


Sure you now need an extra pair of character to access the A, so the
interface is not exactly the same.
But this is how it should have been from day one.

Not sure if and how the above applies to your program, but it is closely
related to the kind of surprise you had in the OP.
 
J

Jakob Bieling

AnalogFile said:
Consider:

class A {
public:
virtual void mutate();
virtual ~A()=0;
static A* New( /* some params */ );
protected:
A();
A(const A&);
A& operator=(const A&);
};
class B {
A *pa;
public:
B()
:pa( A::New(/*params*/) )
{
}

~B() { delete &a; }

A& a() { return *pa; }
const A& a() const { return *pa; }
};
Sure you now need an extra pair of character to access the A, so the
interface is not exactly the same.

Instead of providing those accessors, you also could have worked this
with a special kind of smart pointer. Obviously, it would have to be
special in a way that only class B is allowed to make the smart pointer
delete things. Instead of writing b.a ().foo; you would write
b.a->foo;

I guess it is a matter of preference. Though, imo, using the smart
pointer allows you to hide details of the construction of A and allows
for reuse.

regards
 
A

AnalogFile

Jakob said:
Instead of providing those accessors, you also could have worked this
with a special kind of smart pointer. Obviously, it would have to be
special in a way that only class B is allowed to make the smart pointer
delete things. Instead of writing b.a ().foo; you would write
b.a->foo;

I guess it is a matter of preference. Though, imo, using the smart
pointer allows you to hide details of the construction of A and allows
for reuse.

IMO this is not something to be considered when designing B.
If A is part of the "value" of B, then B should expose A. It's ok that A
is supposed to be allocated dynamically while B gives a reference to an
A instead of a pointer because that's a commonly understood way to
differentiate simple access from ownership transfer.

If smart pointers were to be introduced, that should happen at the
design level of A. And A::New should just not return a plain pointer,
but a smart pointer instead.

But you probably are not really asking for a smart pointer. You are
asking for B::a() to become operator-> of a special B member that will
take the name a. Like this:

struct B {

class indirect {
friend class B;
A *pa;
indirect(A*p):pa(p){}
indirect();
indirect(const indirect&r);
~indirect() { delete pa; }
indirect& operator=(const indirect&);
public:
A* operator->() { return pa; }
const A* operator->() const { return pa; }
} a;

B()
:a( A::New(/*params*/) )
{
}

};

That's ok. It's just a more convoluted way to implement the same
solution: use function overloading to grab constness.

It has the extra trick that plays with operators to make the code more
C/C++ lookalike and less pascal/java/python/ruby/whatever looking.

If you find this more legible and self documenting than the B::a()
method, go ahead.
 
C

Chris Newton

Mark said:
The following compiles without error on four different platforms (Linux
g++, Sun CC, HP aCC, Win Dev-C++) so I suspect it's ok, but I don't see
why this isn't a const-related error. pB is a pointer to constant B and
foo() is a non-constant function invoked on a (reference) member of B.
Can someone explain this?
struct A
{
void foo () {}
};

struct B
{
B () : a(*new A) {}
A& a;
};

struct C
{
C () : pB(new B) {}
void bar () {pB->a.foo();}

const B* pB;
};

int main ()
{
C c;
c.bar();
}

As others have pointed out, the A& isn't const, so there is no violation
of const correctness according to the rules of the language.

This is always a somewhat tricky subject in C++, because from an OO
perspective, there are two common but very different interpretations of
pointer or reference members of a class.

One is a simple indirection, where the object containing the reference
needs to know how to find some other object, but there is no particular
relationship beyond that. This would be common in many data structures,
for example. Here, we usually wouldn't want any constancy applied to the
containing object to apply to its pointers and references as well.

The other interpretation is effectively aggregation, where the
containing object is probably responsible for allocating and releasing
the referenced object, and the latter is treated as a subobject of the
former. This is quite common when you're modelling concrete, real-world
entities using objects, but for some reason you don't want to use a
simple data member for the subobjects. In this case, any constancy
applied to the containing object would, ideally, apply implicitly to any
contained pointers and references, since the pseudo-subobjects should be
treated as constant if their owner is.

Of course, C++ can't tell the difference from the code you give it. As
usual in such situations, it presents the full range of options to the
programmer and trusts that he will not "abuse the privilege". If your
example classes are intended to represent the aggregation case, then you
are committing that abuse by allowing public access to a contained
reference. If you want to make the code safer in that situation, then
the usual idiom is to make the reference member private, and then
provide two overloaded public accessor functions, one const-qualified
and one not, with the former returning the equivalent const reference.

Hope that helps,
Chris
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,710
Latest member
bernietqt

Latest Threads

Top