473,802 Members | 2,172 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

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 1872
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.l earn.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**@NOSPAMkuv a.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**@NOSPAMkuv a.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):p a(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
11881
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 suggestions? My program and the errors are below... #include <ext/hash_map> #include <string>
6
3336
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 my particular application. One of the advantages is that the _compiler_ can force inner matrix dimensions used in multiplication to agree. A _complie-time_ error will be triggered if you write A * B and the number of coluns in A does not equal the...
7
7077
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 if that's your only reason not to help, please do. Otherwise, I guess it's OK. But, just remember, I'm not asking you to do my work for me, just to point out my error. My problem is not with the algorithm itself (standard divide and conquer on...
3
1485
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 algebraic vector class. The problem I encountered there was that algebraic vectors of some concrete dimensions were susceptible to include some extra methods not included in the most generic case. For example, 3-dimensional vectors include the...
3
2056
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 called!"<<endl ;
10
4791
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 structures are read only) Ideally one should be able to put the redundant information there automatically so no mistakes are possible, but in a lot of case I see no way how to do it.
7
2805
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 object of that class. And is that also the reason why if you use class member functions for operators << and >you have to write: someclass << cout; someclass >cin; ?
2
1307
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) {v=((me*)this)->operator+(o); return *(me*)this;} me& operator*=(const me& o) {v=((me*)this)->operator*(o); return *(me*)this;}
22
3623
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 over to the micrcontroller compiler I'm getting all sorts of errors. One thing I'd like to clarify is the need (in C89) for a compile- time constant in the initialiser of a variable. The compiler rejects the following source file: /* Start...
0
9699
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
10536
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, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
10063
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 protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
9114
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
7598
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
6838
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
5494
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
5622
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
3
2966
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

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.