Boost logo

Boost :

From: David B. Held (dheld_at_[hidden])
Date: 2004-01-04 15:47:20


"Thorsten Ottosen" <nesotto_at_[hidden]> wrote in message
news:bt8d39$vah$1_at_sea.gmane.org...
> [...]
> The most important application of propagating constness is when
> storing an object by pointer in a class:
>
> class X
> {
> smart_ptr<My_polymorhic_type> p_;
> public:
> int bar() const
> {
> return p_->this_is_also_a_const_function();
> // compile time error if not
> }
> };
>
> This is the motivating examlple that I have referred to all the time.
> I'm sorry I didn't brought it to your attention earlier. This is what I
> mean by bypassing the type system.

But you're asking the type system to do something that it doesn't
for real pointers. To do what you want, you can just do this:

    int bar() const
    {
        return smart_ptr<My_polymorphic_type const>(p_)->
                this_is_also_a_const_function();
    }

Maybe it's more verbose than you want, but it's essentially
what you'd have to do with a raw pointer, no? You're right that
this is essentially the same as a const_cast<>. But as you also
point out, you have to a const_cast<> no matter which design is
chosen. But we all know that increasing constness is always
safe, whereas decreasing it is often dangerous, and usually
suspect. So if we must have const_cast<>, why not choose the
design which forces the safe direction?

> You and others apparently want to call non-const functions on
> 'p_'. Fine, although a bit strange.(It is strange because one
> could not do it if one stored a string member.)

It's not strange at all. Just because an object is const doesn't
mean that everything it's pointing to must be const. Let's go back
to the parameter example, because it's so common, and thus,
so important:

void foo(smart_ptr<T> const& p);

Your solution is this:

void foo(T& t);
foo(*p);

But there's a problem with this "solution". What is that? The
problem is that you've changed the interface into a form that is
*not equivalent* to the original. Why do we pass pointers in
the first place? Is it just for efficiency? Not in C++. That's what
references are for. No, we pass pointers because *they can
be null*. Sure, there is a way to pass a null value in the second
form, but it requires the use of Boost.Optional, and I hardly think
that is an ideal "solution" to a problem that only exists by breaking
existing smart pointer designs.

> And the solution would be to make 'p_' mutable. This is what
> mutable is there for afterall.

But it only solves the problem you introduce. Note that mutable
does nothing for parameter passing.

> [...]
> Assuming my suggestion, how would you reset the pointer? You
> can't assign to your argument. You cannot call reset() because it
> is non-const.

My point is not that your solution does not prevent reset() and such.
I'm saying that it does not prevent reset() and other non-const
operations *when the pointee needs to be modified*. So in your
design, you either get all const or no const. Therefore, if someone
wants to pass by pointer, they have to do this:

void foo(smart_ptr<T>& p);

Well, that certainly documents that *p can be changed, but it also
allows p to be changed. That's because protecting p, like so:

void foo(smart_ptr<T> const& p);

would imply that *p is also const, even if it shouldn't be. I already
explained why you don't want to simply forbid users from using
this style.

> You would need in order to call the function either a cast or a
> new member which returns a mutable pointer, but that should
> surely be *easy* to provide.

It should be easy but unnecessary. I dare say passing pointers
as params is as frequent if not more so than using them in const
member functions. Making the change you suggest would break
thousands, possibly millions of lines of code. Is it worth it? I
already said which const_cast<> I would prefer to see, and if you
use a smart pointer, it doesn't even have to look like a cast
(because you're really making a copy, after all). It would be nice
if there were no copy penalty, but that's a different ballgame, isn't
it?

> [...]
> In my example above shared_ptr<const T> will not give me what
> I want. I've stated this again and again.

And you've been wrong again and again. Just in case you missed
it the first time, I'll reiterate here:

    int bar() const
    {
        return smart_ptr<T const>(p_)->constFunction();
    }

> [...]
> Again, I would alsway pass my raw pointer by value. The fact
> that you need to use a reference to prohibit a ref-count updates
> really shows that the drop-in replacement is not possible.

Drop-in replacement *is* possible. You use a reference as the
small price you pay for getting an additional feature that is not
available with raw pointers. And you can't always pass by value.

> [...]
> I'm sorry to hear that. :-) I hope I've put some of the maddness
> away in this mail.

I understand your position better, and sympathize with some
related points; but I'm not convinced that deep const should be
the default for all pointers. At best, I think it should be an
optional policy that you can choose to select for applications
such as yours, and that's what I will focus on doing for policy_ptr.

Dave

---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.556 / Virus Database: 348 - Release Date: 12/26/2003

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk