473,395 Members | 1,422 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,395 software developers and data experts.

should this compile? [const]

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();
}
Mar 18 '06 #1
7 1850
Mark P wrote:
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
--
Please remove capital As from my address when replying by mail
Mar 18 '06 #2
Mark P wrote:
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
Mar 18 '06 #3
AnalogFile wrote:
Mark P wrote:
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


Thanks to both you and Victor. That clears it up very well.
Mar 18 '06 #4
Mark P wrote:
AnalogFile wrote:
Mark P wrote:
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.

....
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.

Mar 18 '06 #5
AnalogFile <ne**@NOSPAMkuva.itREALLYNO> wrote:
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
--
jb

(reply address in rot13, unscramble first)
Mar 18 '06 #6
Jakob Bieling wrote:
AnalogFile <ne**@NOSPAMkuva.itREALLYNO> wrote:
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.


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.

Mar 18 '06 #7
Mark P wrote:
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
Mar 18 '06 #8

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

3
by: Mark | last post by:
Hi, I'm trying to use hash_map (gcc 3.2.2) with a std::string as the key. It will compile if I use <map> but I get a bunch of template compile errors when I change it to hash_map. Any...
6
by: Ben Ingram | last post by:
Hi all, I am writing a template matrix class in which the template parameters are the number of rows and number of columns. There are a number of reasons why this is an appropriate tradeoff for...
7
by: Aguilar, James | last post by:
Hello all, To begin, yes, this -is- a homework assignment. However, it is for my Algorithms class, and my instructor has given us explicit permission to use "expert" groups like newsgroups, so...
3
by: Ruben Campos | last post by:
Greetings. Some time ago, I had writing a CVector <T, N> class, which implements an algebraic vector of arbitrary both dimension and scalar type. First, I defined the interface for the generic...
3
by: Yang Zhang | last post by:
Here is a program: ///////////////////////////////////////////////// #include <iostream> using namespace std ; class A { int a ; A(const A& aA) { a=aA.a ; cout<<"copy constructor...
10
by: Bart Goeman | last post by:
Hi, I have a question about how to put redundant information in data structures, initialized at compile time. This is often necessary for performance reasons and can't be done at run time (data...
7
by: Eric Lilja | last post by:
>From a book, I know the following is true for the comparison operators: An overloaded operator that is a class member is only considered when the operator is used with a *left* operand that is an...
2
by: Piotr Sawuk | last post by:
What is wrong with the following? Why doesn't it compile? template<int f, class me, typename ValType=int> class Fm { protected: ValType v; public: me& operator+=(const me& o)...
22
by: Tomás Ó hÉilidhe | last post by:
I've been developing a C89 microcontroller application for a while now and I've been testing its compilation using gcc. I've gotten zero errors and zero warnings with gcc, but now that I've moved...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.