Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Constness for user-defined by-reference types

2 views
Skip to first unread message

Florian Weimer

unread,
Jan 1, 2010, 10:58:00 AM1/1/10
to
What is a good way to express constness for user-defined by-reference
types? Plain const does not work because you can easily make a
non-const object,

void foo (const T& t) {
T t1(t);
// non-const path to t via t1
}

Traditionally, you need two separate types (like iterator and
const_iterator). I wonder if there is something in C++ which would
avoid creating two types.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Balog Pal

unread,
Jan 1, 2010, 5:50:22 PM1/1/10
to
"Florian Weimer" <f...@deneb.enyo.de>

> What is a good way to express constness for user-defined by-reference
> types? Plain const does not work because you can easily make a
> non-const object,
>
> void foo (const T& t) {
> T t1(t);
> // non-const path to t via t1

No, that creates a copy of t.
To create a non-const path to t, you need const_cast. And th code shall not
have those without a good reason.

Florian Weimer

unread,
Jan 3, 2010, 11:54:32 AM1/3/10
to
* Balog Pal:

> "Florian Weimer" <f...@deneb.enyo.de>
>> What is a good way to express constness for user-defined by-reference
>> types? Plain const does not work because you can easily make a
>> non-const object,
>>
>> void foo (const T& t) {
>> T t1(t);
>> // non-const path to t via t1
>
> No, that creates a copy of t.

Not really, it's just a shallow copy if T has reference-like
semantics, like an iterator.

SG

unread,
Jan 3, 2010, 5:15:00 PM1/3/10
to
On 3 Jan., 17:54, Florian Weimer wrote:

> Balog Pal wrote:
> > "Florian Weimer" <f...@deneb.enyo.de>
> >> What is a good way to express constness for user-defined by-reference
> >> types? Plain const does not work because you can easily make a
> >> non-const object,
> >>
> >> void foo (const T& t) {
> >> T t1(t);
> >> // non-const path to t via t1
>
> > No, that creates a copy of t.
>
> Not really, it's just a shallow copy if T has reference-like
> semantics, like an iterator.

Even if T is an iterator type t1 will be a copy of t -- with whatever
semantics T's copy ctor has. It seems you confuse

U const * // Type1

with

U * const // Type2

Top-level constness of an iterator should only affect the iterator
itself, not the object it refers to. At least that's the idea as
iterators are a generalization of pointers. Don't use Type2 if you
want Type1. In your case it means: The use of two different user-
defined types may be the best solution -- just like containers offer
associated types "iterator" and "const_iterator". Note: "const"
doesn't refer to the top level constness in this case which might be
one reason for the confusion. So, "const iterator" and
"const_iterator" are different types.

Cheers,
SG

Florian Weimer

unread,
Jan 4, 2010, 3:52:14 PM1/4/10
to
* SG:

> Even if T is an iterator type t1 will be a copy of t -- with whatever
> semantics T's copy ctor has. It seems you confuse
>
> U const * // Type1
>
> with
>
> U * const // Type2
>
> Top-level constness of an iterator should only affect the iterator
> itself, not the object it refers to.

Sure, and that's my problem. I don't want to implement a
const_iterator class for every iterator-like class I create (where
copies are only shallow copies, so that it's trivial to bypass
conceptual const-ness). Is there a way to achieve this in some rather
general way, without exposing too much of the implementation in the
header files (that is, it should mix with the PIMPL idiom)?

Bart van Ingen Schenau

unread,
Jan 5, 2010, 2:16:42 PM1/5/10
to
On Jan 4, 9:52 pm, Florian Weimer <f...@deneb.enyo.de> wrote:
> * SG:
>
> > Even if T is an iterator type t1 will be a copy of t -- with whatever
> > semantics T's copy ctor has. It seems you confuse
>
> > U const * // Type1
>
> > with
>
> > U * const // Type2
>
> > Top-level constness of an iterator should only affect the iterator
> > itself, not the object it refers to.
>
> Sure, and that's my problem. I don't want to implement a
> const_iterator class for every iterator-like class I create (where
> copies are only shallow copies, so that it's trivial to bypass
> conceptual const-ness). Is there a way to achieve this in some rather
> general way, without exposing too much of the implementation in the
> header files (that is, it should mix with the PIMPL idiom)?

As I understand it, you want a single type that implements both the
model "pointer to non-const element" and the model "pointer to const
element". As those two models contradict each other, it is
fundamentally not possible to use a single type that implements them
both.

Bart v Ingen Schenau

Jyoti Sharma

unread,
Jan 5, 2010, 2:16:35 PM1/5/10
to

"Florian Weimer" <f...@deneb.enyo.de> wrote in message
news:87my0yt...@mid.deneb.enyo.de...

> What is a good way to express constness for user-defined by-reference
> types? Plain const does not work because you can easily make a
> non-const object,
>
> void foo (const T& t) {
> T t1(t);
> // non-const path to t via t1
> }
>

t1 is not a path to t.
If T is a primitive type t1 is another object of type T that will have value
same as that of t.
If T is a class and it has a user-defined copy constructor the argument type
should be const T& to work in this case.

Am I missing something?

Regards,
Jyoti

Edward Rosten

unread,
Jan 5, 2010, 2:18:11 PM1/5/10
to
On Jan 3, 10:15 pm, SG <s.gesem...@gmail.com> wrote:

> Even if T is an iterator type t1 will be a copy of t -- with whatever
> semantics T's copy ctor has. It seems you confuse
>
> U const * // Type1
>
> with
>
> U * const // Type2

The OP is not confused about the semantics and has not misunderstood
the problem. You can see that by the way that his example in the
original post and subsequent post is correct. Every time this topic
comes up, people keep on claiming that this kind of complaint is based
on a misunderstanding of how C++ works. Apparently no amount of
completely correct exposition seems to convince people that this is
not a misunderstanding.

The OP is instead _complaining_ about how the semantics work. Not
misunderstanding them. He wants C++ to provide a mechanism to make the
semantics of his classes a bit different.


The complaint restated is this:

Imagine you have a type with copy semantics eg, std::vector. You can
write a function:

void foo(const vector<int>& i);

i behaves like it is conceptually const. There is no way of modifying i
[0]. Now imagine you have a class with reference semantics, eg a
reference counted version of std::vector. It may for instance
internally be implemented with a shared_ptr<vector<int> > and forward
on all the functions of vector, eg:

template<class C> class refvector
{
private:
shared_ptr<vector<C> > vec;

public:
refvector()
:vec(new vector<C>)
{}

C& operator[](size_t i)
{
return (*vec)[i];
}

const C& operator[](size_t i) const
{
return (*vec)[i];
}
// etc
};

If you write a function like this:

void bar(const refvector<int>& j)
{
refvector<int> nonconst_j(j);
nonconst_j[0] = 0xdeadbeef; //conceptually modify j.
}

Then j is not conceptually const, as illustrated, despite the
similarity between the interface of vector and refvector and the
similarity between the function definitions. The complaint is that C++
has no mechanism for making the two examples have the same semantics.
I.e. it has no mechanism for making the top-level const stronger.

Of course is possible to write instead:

void bar2(const refvector<const int>& j);

and make sure that that refvector<int> knows how to turn itself in to
a refvector<const int>. Of course, the code looks different, meaning
that the uniformity of interface is spoiled. Imagine writing a rather
generic function which works on vector like objects, provided that
they provide operator[] and a .size() method:

One may wish to write the function like this:

template<class T> int sum_vector_like_object(const T& v)
{
//make a temp copy of v
T tmp_v(v);
//sort_inplace is a domain specific sorting function which also
uses operator[] and .size()
sort_inplace(v); //This improves the precision for summing an array
of positive reals
double sum=0;
for(int i=0; i < tmp_v.size(); i++)
sum += tmp_v[i];
return sum;
}

If one initially write the function with std::vector and std::valarray
in mind and then passes a refvector, the code is broken in that the
argument is modified. The problem is that C++ does not allow
sufficient control over the semantics for one to design refvector that
produces an error in the above function.

Essentially what is missing is the ability to optionally make top-
level const stronger so that it propagates further, allowing one to
use const to further protect oneself from one's own mistakes.

This is not based on a misunderstanding of C++ semantics. It is a
complaint based on the inability to make reference classes
conceptually const like value classes. This is a complaint I happen to
agree with.

-Ed
--
(You can't go wrong with psycho-rats.)(http://mi.eng.cam.ac.uk/~er258)

/d{def}def/f{/Times s selectfont}d/s{11}d/r{roll}d f 2/m{moveto}d -1
r 230 350 m 0 1 179{ 1 index show 88 rotate 4 mul 0 rmoveto}for/s 12
d f pop 235 420 translate 0 0 moveto 1 2 scale show showpage

Öö Tiib

unread,
Jan 5, 2010, 7:58:24 PM1/5/10
to
On 4 Jan, 22:52, Florian Weimer <f...@deneb.enyo.de> wrote:
> * SG:
>
> > Top-level constness of an iterator should only affect the iterator
> > itself, not the object it refers to.
>
> Sure, and that's my problem. I don't want to implement a
> const_iterator class for every iterator-like class I create (where
> copies are only shallow copies, so that it's trivial to bypass
> conceptual const-ness).

People who are reading/using your code usually expect that you follow
the good traditions. Tradtions like that for exaple that there are
const_iterator and iterator types for your container-like class.

If you are writing for yourself then of course you may achieve
whatever semantics you please by overloading operators of your
iterator. Others would be confused by the result (as well as you
yourself will be few years later) but so what?

pfultz2

unread,
Jan 6, 2010, 3:41:55 AM1/6/10
to
> [ Seehttp://www.gotw.ca/resources/clcm.htmfor info about ]

> [ comp.lang.c++.moderated. First time posters: Do this! ]

This kinda of deep const can be achieved in c++, but it requieres
rvalues to achieve it smoothly. Plus, when you use this type of deep
const pointer in another class you will have to write the rvalue
constructor since the compiler won't unfortunately generate it
automatically.
Now, generic programming needs to be approached differently with deep
const semantics in mind(of course this wasnt in mind in the stl
library) For example if you want create a vector of deep const
pointers:
vector<deep_ptr<foo>> bar;
bar.push_back(deep_ptr<foo>(new foo()));
Now this is not possible, because push_back uses a const ref to the
deep_ptr, and so it cant copy it into the vector. So there is a
difference between copyable and const copyable. A simple solution is
to create a class to select the right type. This can work since the
type is deduced from the vector. If the type can't be deduced then a
rvalue reference will work. And then if you want to completely
restrict changes then use the const ref.
Also, you dont need to write two classes for itereator and
const_iterator. Just write the class for itereator and use a
const_iterator_wrapper to create the const_iterator.

SG

unread,
Jan 6, 2010, 1:10:22 PM1/6/10
to
On 5 Jan., 20:18, Edward Rosten <edward.ros...@gmail.com> wrote:
> On Jan 3, 10:15 pm, SG <s.gesem...@gmail.com> wrote:
>
> > [...] It seems you confuse

> > U const * // Type1
> > with
> > U * const // Type2
>
> The OP is not confused about the semantics and has not misunderstood
> the problem.

That's your opinion.

> You can see that by the way that his example in the
> original post and subsequent post is correct.

Correct in what way? Did you read the comment? He uses the name of the
proxy object interchangably for the proxy object and the object it
refers to. I wouldn't call that "correct". It's more of an indication
that the OP doesn't distinguish between two different objects even
though the top level constness only applies to one of them, the proxy.

> The OP is instead _complaining_ about how the semantics work.

He is complaining about having to write two classes for two models of
two different but related concepts. He could try to solve this with a
parameterized class (aka class template, write one template, get two
models) or just check out how Boost's iterator libraries might help in
developing iterators with less boiler plate code. Anything that
involves misusing the top-level const is "just wrong" in my opinion.

> [...]


> If you write a function like this:
>
> void bar(const refvector<int>& j)
> {
> refvector<int> nonconst_j(j);
> nonconst_j[0] = 0xdeadbeef; //conceptually modify j.
> }

That's what I'd call bad design -- possibly rooted in a distorted
perception of the concepts involved (something along the lines of not
getting the difference between pointer and pointee).

btw, your name looks familiar. I'm sure we discussed this already
which is why I don't really expect this discussion to go anywhere.
Obviously we both kept our views and it's probably going to stay that
way.

> Then j is not conceptually const, as illustrated, despite the
> similarity between the interface of vector and refvector and the
> similarity between the function definitions. The complaint is that C++
> has no mechanism for making the two examples have the same semantics.
> I.e. it has no mechanism for making the top-level const stronger.

You blame C++. I blame you for a bad design. You want a spoon that
looks and feels exactly like a fork but you still want it to be a
spoon. I say, get over it and take a fork already. (I'm generally not
a fan of analogies but this one seems somewhat appropriate.)

Cheers,
SG

Andrei Alexandrescu

unread,
Jan 6, 2010, 7:19:17 PM1/6/10
to
SG wrote:
> On 5 Jan., 20:18, Edward Rosten <edward.ros...@gmail.com> wrote:
>> On Jan 3, 10:15 pm, SG <s.gesem...@gmail.com> wrote:
>>
>>> [...] It seems you confuse
>>> U const * // Type1
>>> with
>>> U * const // Type2
>> The OP is not confused about the semantics and has not misunderstood
>> the problem.
>
> That's your opinion.
>
>> You can see that by the way that his example in the
>> original post and subsequent post is correct.
>
> Correct in what way? Did you read the comment? He uses the name of the
> proxy object interchangably for the proxy object and the object it
> refers to. I wouldn't call that "correct". It's more of an indication
> that the OP doesn't distinguish between two different objects even
> though the top level constness only applies to one of them, the proxy.

[snip]

> You blame C++. I blame you for a bad design. You want a spoon that
> looks and feels exactly like a fork but you still want it to be a
> spoon. I say, get over it and take a fork already. (I'm generally not
> a fan of analogies but this one seems somewhat appropriate.)

My understanding is that the complaint stems from C++'s const being
non-transitive. Is that correct? If that's the case, I think that's
actually a valid concern.

Andrei

Balog Pal

unread,
Jan 7, 2010, 4:32:52 AM1/7/10
to
"Andrei Alexandrescu" <SeeWebsit...@erdani.org>

>> You blame C++. I blame you for a bad design. You want a spoon that
>> looks and feels exactly like a fork but you still want it to be a
>> spoon. I say, get over it and take a fork already. (I'm generally not
>> a fan of analogies but this one seems somewhat appropriate.)
>
> My understanding is that the complaint stems from C++'s const being
> non-transitive. Is that correct? If that's the case, I think that's
> actually a valid concern.

It's more like complaint on const being 'shallow'. What is hardly news, or
should not be.

And IMO it is not so valid a concern -- would we like a fixed language,
where top level const propagates all the way? So you could have only char *
and const char * const, but not the other two variants?

The first post had a factual error in the comment.

And later explanations revelaed that indeed OP wanted a single class behave
for two distinct uses -- if the underlying class was fit the explanation it
had a serious design flaw.

There are "valid concerns" or painful areas with smart pointer-like things
where they fail to adjust constness on different levels, unlike the regular
pointers. But it is not OP's problem, at least as I got it.

Andrei Alexandrescu

unread,
Jan 7, 2010, 1:12:21 PM1/7/10
to
Balog Pal wrote:
> "Andrei Alexandrescu" <SeeWebsit...@erdani.org>
>>> You blame C++. I blame you for a bad design. You want a spoon
>>> that looks and feels exactly like a fork but you still want it to
>>> be a spoon. I say, get over it and take a fork already. (I'm
>>> generally not a fan of analogies but this one seems somewhat
>>> appropriate.)
>>
>> My understanding is that the complaint stems from C++'s const being
>> non-transitive. Is that correct? If that's the case, I think
>> that's actually a valid concern.
>
> It's more like complaint on const being 'shallow'. What is hardly
> news, or should not be.
>
> And IMO it is not so valid a concern -- would we like a fixed
> language, where top level const propagates all the way?

I guess some do :o). D does exactly that, to the end of obtaining strong
guarantees regarding object mutability and cross-thread sharing.

> So you could have only char * and const char * const, but not the
> other two variants?

With transitive const you can have char* (mutable pointer to mutable
character), char const* (mutable pointer to immutable character), and
const char const* (immutable pointer to immutable character). What you
lose is char *const (immutable pointer to mutable character), which is
arguably the least interesting among the four. The important property of
transitive const is that once you step into constness by following a
pointer (or a member), you can't step back into mutability. This is an
interesting property because it is exactly what you need e.g. for safe
sharing of immutable data across threads.


Andrei

Edward Rosten

unread,
Jan 7, 2010, 1:11:06 PM1/7/10
to
On Jan 7, 9:32 am, "Balog Pal" <p...@lib.hu> wrote:

> It's more like complaint on const being 'shallow'. What is hardly news, or

yes

> should not be.
>
> And IMO it is not so valid a concern -- would we like a fixed language,
> where top level const propagates all the way? So you could have only char *
> and const char * const, but not the other two variants?

no, not at all.

What I would like is a fixed language where the designer of a class
can choose how deep const goes. Class designers get to choose the
semantics of all sorts of things, so I don't see why constness
shouldn't be on that list too.

-Ed

--
(You can't go wrong with psycho-rats.)(http://mi.eng.cam.ac.uk/~er258)

/d{def}def/f{/Times s selectfont}d/s{11}d/r{roll}d f 2/m{moveto}d -1
r 230 350 m 0 1 179{ 1 index show 88 rotate 4 mul 0 rmoveto}for/s 12
d f pop 235 420 translate 0 0 moveto 1 2 scale show showpage

Edward Rosten

unread,
Jan 7, 2010, 1:10:44 PM1/7/10
to
On Jan 6, 6:10 pm, SG <s.gesem...@gmail.com> wrote:

> > [...]


> > If you write a function like this:
>
> > void bar(const refvector<int>& j)
> > {
> > refvector<int> nonconst_j(j);
> > nonconst_j[0] = 0xdeadbeef; //conceptually modify j.
> > }
>
> That's what I'd call bad design -- possibly rooted in a distorted

I do not know how to make a class behave differently. Perhaps you
could suggest a better design? As far as I know, the only way to make
j conceptually const is to make it a refvector<const int> or some
similar class such as const_refvector<int>, whose implementation is
much the same as refvector, but with a bunch of extra const's
sprinkled around.

> perception of the concepts involved (something along the lines of not
> getting the difference between pointer and pointee).


> btw, your name looks familiar. I'm sure we discussed this already

That is correct.

> which is why I don't really expect this discussion to go anywhere.
> Obviously we both kept our views and it's probably going to stay that
> way.

Well, I have higher hopes :)

> > Then j is not conceptually const, as illustrated, despite the
> > similarity between the interface of vector and refvector and the
> > similarity between the function definitions. The complaint is that C++
> > has no mechanism for making the two examples have the same semantics.
> > I.e. it has no mechanism for making the top-level const stronger.
>
> You blame C++. I blame you for a bad design.

So what is a good design? How can the two examples have the same
semantics?

Please refer to the function I defined in an earlier post:

template<class T> int sum_vector_like_object(const T& v);

The function as I have written it will work fine on a vector<float>
and numeric vector objects from a variety of libraries. By fine, I
mean that the function has no side effects. Clearly it will cause side-
effects with refvector.

-Ed

--
(You can't go wrong with psycho-rats.)(http://mi.eng.cam.ac.uk/~er258)

/d{def}def/f{/Times s selectfont}d/s{11}d/r{roll}d f 2/m{moveto}d -1
r 230 350 m 0 1 179{ 1 index show 88 rotate 4 mul 0 rmoveto}for/s 12
d f pop 235 420 translate 0 0 moveto 1 2 scale show showpage

Edward Rosten

unread,
Jan 7, 2010, 1:32:32 PM1/7/10
to
On Jan 7, 12:19 am, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:

> My understanding is that the complaint stems from C++'s const being
> non-transitive. Is that correct? If that's the case, I think that's
> actually a valid concern.

What do you mean by transitive?

-Ed

--
(You can't go wrong with psycho-rats.)(http://mi.eng.cam.ac.uk/~er258)

/d{def}def/f{/Times s selectfont}d/s{11}d/r{roll}d f 2/m{moveto}d -1
r 230 350 m 0 1 179{ 1 index show 88 rotate 4 mul 0 rmoveto}for/s 12
d f pop 235 420 translate 0 0 moveto 1 2 scale show showpage

SG

unread,
Jan 7, 2010, 1:32:32 PM1/7/10
to
Andrei Alexandrescu wrote:
>
> My understanding is that the complaint stems from C++'s const being
> non-transitive. Is that correct?

I don't think it is. Also, the word "transitive" seems inappropriate
to describe whether top-level constness of a pointer allows the
pointee to be changed trough that pointer. Transitivity is a property
of some binary relations. How does this apply to pointers and
constness?

When I query an internet search enginge, all that pops up are sites
promoting the D programming language with "transitive const" as one of
its features. If I remember correctly, Bartosz Milewski did a blog
post on the subject including a comparison with C++. But he did not
acknowledge at first that in C++ this only applies to pointers and
that C++ supports "real members" (subobjects inside another object to
which const is actually propagated) as opposed to trees of references
you have in other languages (Java, D, ...). So, from my perspective, D
has "transitive const" to emulate const propagation to "real members".
With this understanding the keyword "mutable" in D for overriding this
is actually another way of saying "The object this member refers to is
not a part of me".

Even if the OP were to write a pointer-like class template that
propagates const to the pointee (which is of course possible), it
wouldn't solve the "problem". He actually did write something similar,
a reference-like class that propagates constness to its referent.
Instead of using two distinct cv-unqualified types for read-only
access and read-write access, he wants to misuse the top-level
constness to make this distinction. It obviously doesn't work because
he can create a non-const copy of every const proxy object which
results in a broken const correctness (write access is possible
without const_cast).

Cheers,
SG

Andrei Alexandrescu

unread,
Jan 8, 2010, 1:56:24 AM1/8/10
to
Edward Rosten wrote:
> On Jan 7, 12:19 am, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>
>> My understanding is that the complaint stems from C++'s const being
>> non-transitive. Is that correct? If that's the case, I think that's
>> actually a valid concern.
>
> What do you mean by transitive?

If something is const, anything reachable from it by following
indirections is also const.

Also mind the obligatory http://tinyurl.com/yco6own :o).


Andrei

Andrei Alexandrescu

unread,
Jan 8, 2010, 1:57:29 AM1/8/10
to
SG wrote:
> Andrei Alexandrescu wrote:
>> My understanding is that the complaint stems from C++'s const being
>> non-transitive. Is that correct?
>
> I don't think it is. Also, the word "transitive" seems inappropriate
> to describe whether top-level constness of a pointer allows the
> pointee to be changed trough that pointer. Transitivity is a property
> of some binary relations. How does this apply to pointers and
> constness?

It's about the transitive state of the object, or transitivity over the
"refers-to" relation. The term is in use already, see e.g.
http://scholar.google.com/scholar?hl=en&q=transitive+readonly&btnG=Search&as_sdt=2000&as_ylo=&as_vis=0

> When I query an internet search enginge, all that pops up are sites
> promoting the D programming language with "transitive const" as one of
> its features. If I remember correctly, Bartosz Milewski did a blog
> post on the subject including a comparison with C++. But he did not
> acknowledge at first that in C++ this only applies to pointers and
> that C++ supports "real members" (subobjects inside another object to
> which const is actually propagated) as opposed to trees of references
> you have in other languages (Java, D, ...). So, from my perspective, D
> has "transitive const" to emulate const propagation to "real members".
> With this understanding the keyword "mutable" in D for overriding this
> is actually another way of saying "The object this member refers to is
> not a part of me".

There are three mistakes above. First, there's no need to emulate const
propagation to "real members" in D since D already has in-situ
membership so the point is moot. Second, D has no mutable keyword :o).
Third, the interesting properties of transitive const start, don't end,
when pointers (and generally indirections) are being followed. Having
in-situ fields or not only opens a discussion around an uninteresting
part of the problem.

> Even if the OP were to write a pointer-like class template that
> propagates const to the pointee (which is of course possible), it
> wouldn't solve the "problem". He actually did write something similar,
> a reference-like class that propagates constness to its referent.
> Instead of using two distinct cv-unqualified types for read-only
> access and read-write access, he wants to misuse the top-level
> constness to make this distinction. It obviously doesn't work because
> he can create a non-const copy of every const proxy object which
> results in a broken const correctness (write access is possible
> without const_cast).

So part of the problem seems to be that non-const copies can be created
unwittingly from const objects, which is not always possible in a
transitive const system.


Andrei

Öö Tiib

unread,
Jan 8, 2010, 8:46:07 AM1/8/10
to
On Jan 7, 8:32 pm, SG <s.gesem...@gmail.com> wrote:
> Instead of using two distinct cv-unqualified types for read-only
> access and read-write access, he wants to misuse the top-level
> constness to make this distinction. It obviously doesn't work because
> he can create a non-const copy of every const proxy object which
> results in a broken const correctness (write access is possible
> without const_cast).


Why broken? Its broken only because it is copyable single type and so
illegal conversions cannot be blocked compile-time. Run-time these can
be checked.
Let me illustrate:

// types for Weirdo & comrades
typedef int* Target;
typedef MisVector<Target> Factory;
typedef Bag::iterator Weirdo;

// factories of Weirdoes
Factory iteratable;
Factory const constIteratable;

// valid Weirdoes
Weirdo iterateIteratable( iteratable.begin() );
Weirdo const constIterateIteratable( iteratable.begin() );
Weirdo const constIterateConstIteratable( constIteratable.begin() );

// illegal conversion that compiles
Weirdo iterateConstIteratable( constIteratable.begin() );

// Weirdo in action
++iterateIteratable; // valid and doable
++constIterateIteratable; // valid and doable
++constIterateConstIteratable; // valid and doable
++iterateConstIteratable; // invalid and can be made to throw

Result is of course bigger, slower, uglier, and more annoying than
normal pair of iterators but works. Pun to maintainer. Lots of C++
code out there seems to be written as entertainment for maintainer so
in that light it is even nothing original.

SG

unread,
Jan 8, 2010, 10:29:29 PM1/8/10
to
Andrei Alexandrescu wrote:
> SG wrote:
> > [...] the word "transitive" seems inappropriate

> > to describe whether top-level constness of a pointer allows the
> > pointee to be changed trough that pointer. Transitivity is a property
> > of some binary relations. How does this apply to pointers and
> > constness?
>
> It's about the transitive state of the object, or transitivity over the
> "refers-to" relation. The term is in use already, see e.g.
> http://scholar.google.com/scholar?hl=en&q=transitive+readonly&btnG=Se...

First, you use "transitive" as a property of a "state" then you
mention the "refers-to" relation. Even ignoring const, the refers-to
relation is certainly not transitive. Simple counter example: A refers
to B, B refers to C but A doesn't have to refer (directly) to C. You
possibly meant the "directly or indirectly refers-to" relation. Still,
how's "const" part of that picture?

I know how it is meant but "transitive" still looks like the wrong
term for that. I guess I just have to accept this use of "transitive"
whether I like it or not.

> > When I query an internet search enginge, all that pops up are sites
> > promoting the D programming language with "transitive const" as one of
> > its features. If I remember correctly, Bartosz Milewski did a blog
> > post on the subject including a comparison with C++. But he did not
> > acknowledge at first that in C++ this only applies to pointers and
> > that C++ supports "real members" (subobjects inside another object to
> > which const is actually propagated) as opposed to trees of references
> > you have in other languages (Java, D, ...). So, from my perspective, D
> > has "transitive const" to emulate const propagation to "real members".
> > With this understanding the keyword "mutable" in D for overriding this
> > is actually another way of saying "The object this member refers to is
> > not a part of me".
>
> There are three mistakes above. First, there's no need to emulate const
> propagation to "real members" in D since D already has in-situ
> membership so the point is moot.

You could have been more specific. Are you referring to structs? From
what I understand a variable "of class type" in D is actually a Java-
like nullable/reseatable reference. Or are you implying that it's
possible to create "class-object" members as if they were structs?

> Second, D has no mutable keyword :o).

Interesting. Let me quote Mr Milewski from a year ago:

"It hasn�t been decided yet if and how D will support an escape
mechanism from const transitivity (and constness in general).
Following C++, D could use the mutable type qualifier for that
purpose. This would also solve the problem of memoizing and
caching inside a const object."

I was assuming it has been added since then because it seems useful
(to me at least).

> Third, the interesting properties of transitive const start, don't end,
> when pointers (and generally indirections) are being followed.

I've trouble interpreting this. What do you mean? How does it relate
to what I wrote?

> Having
> in-situ fields or not only opens a discussion around an uninteresting
> part of the problem.

I don't know what "part of the problem" you are referring to. But I'm
sure that "having in-situ fields" to which const is automatically
propagated (regardless of how pointers work) makes the default pointer
semantics w.r.t. const propagation less important. You basically have
three categories of object-relationships:

(1) an object is a logical and physical member of another object.
Const propagation is desired and automatically present in C++.
(2) an object is a logical member but accessed through a pointer
Const propagation is desired but requires hiding the pointer
(private access) and const-overloadinging of accessor
functions in C++.
(3) an object is only referenced through a pointer member and not
logically a member. Const propagation is not desired which
matches the semantics of C/C++ pointers.

So, only in case 2 you have to "override" the default behaviour by
protecting access to the pointer and const-overloading of member
functions. Case 1 is probably the most prominent one in C++. Case 3
applies to pointer-like classes (iterators, etc).

I don't think there's anything wrong with that or that C++ needs
pointers that propagate const "by default". Still, you could write
your own pointer wrapper that behaves differently, if you like.

> So part of the problem seems to be that non-const copies can be created
> unwittingly from const objects, which is not always possible in a
> transitive const system.

I really try to be open-minded about this. So far, those who think C++
has a problem in this department just stated the obvious when
"pointing out the problem" but didn't provide any alternatives to the
language's semantics that would "fix the problem". For me, "the
problem" still boils down to attempts to misuse top-level constness in
a way to emulate two different (cv-unqualified) types: a type for a
read-only proxy and a type for a read-write proxy.

How *could* that work? Const-overloaded constructors? That might
actually be the first somewhat constructive suggestion here. But I
think any attempt to go down the "one proxy class is enough"-road only
leads to more/other problems. Regularity is one. I'd consider a type T
with the following behaviour:

T const x = ...;
T const y = x; // OK
T z = x; // ill-formed, non-const copy constructor
// only takes non-const reference

highly irregular.

If you're interested in algorithms that don't care about whether the
parameter type is std::vector<double> or vector_ref<double> then copy
semantics simply must not be part of that "VectorLike"-concept you
rely on because it obviously differs between the two models.

Cheers,
SG

Balog Pal

unread,
Jan 10, 2010, 2:37:35 PM1/10/10
to
"Andrei Alexandrescu" <SeeWebsit...@erdani.org>

>> And IMO it is not so valid a concern -- would we like a fixed
>> language, where top level const propagates all the way?
>
> I guess some do :o). D does exactly that, to the end of obtaining strong
> guarantees regarding object mutability and cross-thread sharing.
>
>> So you could have only char * and const char * const, but not the
>> other two variants?
>
> With transitive const you can have char* (mutable pointer to mutable
> character), char const* (mutable pointer to immutable character), and
> const char const* (immutable pointer to immutable character).

If so, 'transitive' is a misleading term -- as I undertand its meaning it
goes in all directions. While your propagation goes only in one direction.
I see from other post that it is not new, and also that I'm not the only one
being confused by it -- probably finding a better terminology were a good
idea.

> What you
> lose is char *const (immutable pointer to mutable character), which is
> arguably the least interesting among the four.

Indeed it is probably the least used variant (mostly due to general
unpopularity of top-level const in C++). As I try to use that, you'd find
many instances of those in my code -- and I would be unhappy to lose it.
(Even considering that it shares niche with references, and I use those
whenever possible. But in many cases the pointer I capture in a local may
be NULL. In a different language that has a reference type similar to that
in C++, but allowing NULL indeed that variant would lose ground.)

> The important property of
> transitive const is that once you step into constness by following a
> pointer (or a member), you can't step back into mutability. This is an
> interesting property because it is exactly what you need e.g. for safe
> sharing of immutable data across threads.

That is an interesting idea, I keep thinging of it. As of now for me
constness on "different levels of pointer" are distinct and have their
proper mappings and use cases. When I want to fix a pointer that does not
imply I want to fix the pointee. (local context, function parameters...)

When it comes to members of an aggregate, SG wrote a good summary, we have
both situations. And welcome of propagation is tied to the use case when
we wanted a direct member, but had to use a pointer for some (probably
technical) reason. While for all the rest of situations the semantics tied
to the pointer are different, and better not messed with by the language.
Sometimes I want to share a mutable piece of data with some object
instances, that does not count as their state. (say I have an open stream
to write something, and my objects get a pointer to it -- like the PFA folks
sends arount the Logger and the whole world ;)

Guess the explicit 'mutable' was thought to handle those situations, and if
so, I can envision a system, where propagation is the default -- on another
line I'm in the camp dreaming 'const' shall be default for anything and
'var' or 'mutable' spelled out -- but without that the system sounds lame.

{ edits: quoted signature and banner removed. please remove extraneous material
before posting. -mod }

CornedBee

unread,
Jan 11, 2010, 5:03:34 PM1/11/10
to
On Jan 10, 8:37 pm, "Balog Pal" <p...@lib.hu> wrote:
> "Andrei Alexandrescu" <SeeWebsiteForEm...@erdani.org>

>
> > With transitive const you can have char* (mutable pointer to mutable
> > character), char const* (mutable pointer to immutable character), and
> > const char const* (immutable pointer to immutable character).
>
> If so, 'transitive' is a misleading term -- as I undertand its meaning it
> goes in all directions. While your propagation goes only in one direction.
> I see from other post that it is not new, and also that I'm not the only one
> being confused by it -- probably finding a better terminology were a good
> idea.

A correct term would be "inherited through the points-to relation",
but "inherited" is such a loaded term in languages supporting object-
orientation that it is probably avoided for this reason.

Sebastian

0 new messages