We need const_shared_ptr<T>

156 views
Skip to first unread message

Alexey Mamontov

unread,
Aug 24, 2016, 3:39:33 AM8/24/16
to ISO C++ Standard - Future Proposals
There is an interesting concept of pointers pair: const_shared with mutable_unique.
Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
mutable_unique is also move constructible from the const shared, while the following logic is perfomed: if the source is the last reference, then the same pointer is moved to the mutable instance, otherwise it creates a copy using copy-constructor. This allowes to modify local instances while paying the lowest possible price, and making copies only if there are some other users of the same instance exist.
Returning a message body as const_share_ptr<vector<uint8>> might be a good example, if there are further modifications of the body allowed. The actual copying would be reduced to minimum.
Modiying local instance would look like the following:

const_shared_ptr<T> Modify(const_shared_ptr<T> val)
{
  mutable_unique_ptr<T> tmp = move(val);
  tmp->MakeChanges();
  return move(tmp);
}

In case of the only instance moved to the function it would provide the best perfomance while moving the same instance around.

It would be best to integrate with std::unique_ptr but since it takes only the deleter in traits it is impossible to support it while we have to make allocations for copy-constructed instances. In our code we have to define a new type to pair the const_shared_ptr. The following are our public interfaces:

template<typename T> class mutable_unique_ptr;
template<typename T> class const_shared_ptr;

template<typename T>
class mutable_unique_ptr
{
 typedef T value_type;
 typedef const_shared_ptr<value_type> const_shared_ptr_type;
 
 mutable_unique_ptr(std::nullptr_t = nullptr);
 mutable_unique_ptr(this_type && src);
 explicit mutable_unique_ptr(value_type * pvalue);
 mutable_unique_ptr(const_shared_ptr_type && src);
 ~mutable_unique_ptr();
 
 this_type & operator=(std::nullptr_t other);
 this_type & operator=(this_type && other);
 this_type & operator=(const_shared_ptr_type && other);
 void swap(this_type & other);
 void reset(value_type * pvalue);
 operator bool() const;
 value_type * operator->() const;
 value_type & operator*() const;
 value_type * get() const;
};
template<typename T>
class const_shared_ptr
{
 typedef T mutable_value_type;
 typedef const mutable_value_type value_type;
 typedef mutable_unique_ptr<mutable_value_type> mutable_value_ptr_type;
 const_shared_ptr(std::nullptr_t = nullptr);
 const_shared_ptr(const this_type & src);
 const_shared_ptr(this_type && src);
 const_shared_ptr(mutable_value_ptr_type && src);
 explicit const_shared_ptr(mutable_value_type * pvalue);
 ~const_shared_ptr();
 
 this_type & operator=(std::nullptr_t other);
 this_type & operator=(const this_type & other);
 this_type & operator=(this_type && other);
 this_type & operator=(mutable_value_ptr_type && other);
 void swap(this_type & other);
 void reset(mutable_value_type * pvalue);
 operator bool() const;
 value_type * operator->() const;
 value_type & operator*() const;
 value_type * get() const;
};

We did not add support for custom allocators, we don't need it. But if it finds it's way to the std, it should be initiated with allocator trait.
Thanks!

Nicol Bolas

unread,
Aug 24, 2016, 10:45:12 AM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
There is an interesting concept of pointers pair: const_shared with mutable_unique. 
Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).

Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.

So `shared_ptr<const T>` seems just as unchangeable as your proposed `const_shared_ptr<T>`.
 
mutable_unique is also move constructible from the const shared, while the following logic is perfomed: if the source is the last reference, then the same pointer is moved to the mutable instance, otherwise it creates a copy using copy-constructor. This allowes to modify local instances while paying the lowest possible price, and making copies only if there are some other users of the same instance exist.

I'm not sure I understand the conceptual foundation of what you're asking for. I understand what all the pieces do; I just don't understand the problem you're solving. In particular, why would constructing a `mutable_unique` from a `const_shared_ptr` invoke copy construction? If you're copying the value, then you're not accessing it by reference. So is it a value type or a reference type?

Or is this some form of copy-on-write value semantics, generalized to all types? If that's the idea behind this, then the names of these types are completely wrong. They're not smart pointers; they're values. And the fact that the const value is being "shared" is an implementation detail.

Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.

So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.

Of course, that also raises the question of a `weak_ptr` equivalent. Does this type need such a thing? With COW strings, the question was moot; strings are dumb arrays, so there was never any question of whether a COW string could have a circular reference to itself. But with generalized COW objects, that possibility now exists.

Edward Catmur

unread,
Aug 24, 2016, 7:57:33 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, 24 August 2016 15:45:12 UTC+1, Nicol Bolas wrote:
> On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
>
> There is an interesting concept of pointers pair: const_shared with mutable_unique. 
>
> Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
>
> Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.

But there can be an already existing shared_ptr<T> aliasing your shared_ptr<T const>. The proposal here is that const_shared_ptr<T> can only be aliased by other const_shared_ptr<T>s.


> > mutable_unique is also move constructible from the const shared, while the following logic is perfomed: if the source is the last reference, then the same pointer is moved to the mutable instance, otherwise it creates a copy using copy-constructor. This allowes to modify local instances while paying the lowest possible price, and making copies only if there are some other users of the same instance exist.
>
> I'm not sure I understand the conceptual foundation of what you're asking for. I understand what all the pieces do; I just don't understand the problem you're solving. In particular, why would constructing a `mutable_unique` from a `const_shared_ptr` invoke copy construction? If you're copying the value, then you're not accessing it by reference. So is it a value type or a reference type?

In order to ensure that a const pointer cannot have its pointee concurrently modified by a mutable pointer.

> Or is this some form of copy-on-write value semantics, generalized to all types? If that's the idea behind this, then the names of these types are completely wrong. They're not smart pointers; they're values. And the fact that the const value is being "shared" is an implementation detail.

Well, other than the clone operation being an observable side effect.

> Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>
> So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.

Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".

> Of course, that also raises the question of a `weak_ptr` equivalent. Does this type need such a thing? With COW strings, the question was moot; strings are dumb arrays, so there was never any question of whether a COW string could have a circular reference to itself. But with generalized COW objects, that possibility now exists.

Would such a weak_value<T const> be invalidated if all the shared_value<T const>s are converted to mutable?

Nicol Bolas

unread,
Aug 24, 2016, 8:54:52 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, August 24, 2016 at 7:57:33 PM UTC-4, Edward Catmur wrote:
On Wednesday, 24 August 2016 15:45:12 UTC+1, Nicol Bolas  wrote:
> On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
>
> There is an interesting concept of pointers pair: const_shared with mutable_unique. 
>
> Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
>
> Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.

But there can be an already existing shared_ptr<T> aliasing your shared_ptr<T const>.

Without a `const_cast` or similar? If you do `make_shared<const T>(...)`, I have no idea how you would get the aliasing to `shared_ptr<T>` without a `const_cast` or similar.

At least, not unless it's a pointer to a different `T`.
 
The proposal here is that const_shared_ptr<T> can only be aliased by other const_shared_ptr<T>s.

> > mutable_unique is also move constructible from the const shared, while the following logic is perfomed: if the source is the last reference, then the same pointer is moved to the mutable instance, otherwise it creates a copy using copy-constructor. This allowes to modify local instances while paying the lowest possible price, and making copies only if there are some other users of the same instance exist.
>
> I'm not sure I understand the conceptual foundation of what you're asking for. I understand what all the pieces do; I just don't understand the problem you're solving. In particular, why would constructing a `mutable_unique` from a `const_shared_ptr` invoke copy construction? If you're copying the value, then you're not accessing it by reference. So is it a value type or a reference type?

In order to ensure that a const pointer cannot have its pointee concurrently modified by a mutable pointer.

Except that it can. The OP made it clear that if you take the last `const_shared_ptr` and turn it into a `mutable_unique`, then the `mutable_unique` doesn't copy the value; it merely takes ownership of the pointer from the `const_shared_ptr`. So yes, it is possible to modify the supposedly const object.

Just not directly through `const_shared_ptr`.

Structurally, this all seems very much like a generalized COW, with the optimization that the last reference to the shared object doesn't need to copy when it tries to write to it.
 
> Or is this some form of copy-on-write value semantics, generalized to all types? If that's the idea behind this, then the names of these types are completely wrong. They're not smart pointers; they're values. And the fact that the const value is being "shared" is an implementation detail.

Well, other than the clone operation being an observable side effect.

> Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>
> So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.

Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".

Conceptually, it's a value type, so it wouldn't have aliasing of any form. What would that even mean? Would you be aliasing a value with another value? Indeed, the OP excluded aliasing from `const_shared_ptr`'s interface, since it doesn't make sense.

After all, if your `const_shared_ptr<T>` actually owns some object of type `U`, what would it mean to make a `mutable_unique` copy? Are you asking to make a copy of the aliased pointer or the owned object? The latter is a bit problematic, since it has been type-erased by that point.

> Of course, that also raises the question of a `weak_ptr` equivalent. Does this type need such a thing? With COW strings, the question was moot; strings are dumb arrays, so there was never any question of whether a COW string could have a circular reference to itself. But with generalized COW objects, that possibility now exists.

Would such a weak_value<T const> be invalidated if all the shared_value<T const>s are converted to mutable?

They would have to be, since there are no shared values out there for it to reference.

Edward Catmur

unread,
Aug 25, 2016, 3:29:28 AM8/25/16
to std-pr...@isocpp.org

On 25 Aug 2016 1:54 a.m., "Nicol Bolas" <jmck...@gmail.com> wrote:
>
> On Wednesday, August 24, 2016 at 7:57:33 PM UTC-4, Edward Catmur wrote:
>>
>> On Wednesday, 24 August 2016 15:45:12 UTC+1, Nicol Bolas  wrote:
>> > On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
>> >
>> > There is an interesting concept of pointers pair: const_shared with mutable_unique. 
>> >
>> > Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
>> >
>> > Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.
>>
>> But there can be an already existing shared_ptr<T> aliasing your shared_ptr<T const>.
>
>
> Without a `const_cast` or similar? If you do `make_shared<const T>(...)`, I have no idea how you would get the aliasing to `shared_ptr<T>` without a `const_cast` or similar.
>
> At least, not unless it's a pointer to a different `T`.

If you do make_shared<T>(...) then you can get a shared_ptr<T const> later that has the same target as the original shared_ptr<T>.

>>
>> The proposal here is that const_shared_ptr<T> can only be aliased by other const_shared_ptr<T>s.
>>
>> > > mutable_unique is also move constructible from the const shared, while the following logic is perfomed: if the source is the last reference, then the same pointer is moved to the mutable instance, otherwise it creates a copy using copy-constructor. This allowes to modify local instances while paying the lowest possible price, and making copies only if there are some other users of the same instance exist.
>> >
>> > I'm not sure I understand the conceptual foundation of what you're asking for. I understand what all the pieces do; I just don't understand the problem you're solving. In particular, why would constructing a `mutable_unique` from a `const_shared_ptr` invoke copy construction? If you're copying the value, then you're not accessing it by reference. So is it a value type or a reference type?
>>
>> In order to ensure that a const pointer cannot have its pointee concurrently modified by a mutable pointer.
>
>
> Except that it can. The OP made it clear that if you take the last `const_shared_ptr` and turn it into a `mutable_unique`, then the `mutable_unique` doesn't copy the value; it merely takes ownership of the pointer from the `const_shared_ptr`. So yes, it is possible to modify the supposedly const object.
>
> Just not directly through `const_shared_ptr`.

OK, but the point is that that modification is not observable to anyone who has a const_shared_ptr.

> Structurally, this all seems very much like a generalized COW, with the optimization that the last reference to the shared object doesn't need to copy when it tries to write to it.

Yes. There's also some similarity to the reader-writer lock model.

>> > Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>> >
>> > So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.
>>
>> Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".
>
>
> Conceptually, it's a value type, so it wouldn't have aliasing of any form. What would that even mean? Would you be aliasing a value with another value? Indeed, the OP excluded aliasing from `const_shared_ptr`'s interface, since it doesn't make sense.

Sorry, I didn't mean aliasing in the sense of creating a shared_ptr to a subobject, rather in the general sense of one pointer having the same target as another pointer, or one reference having the same referent as another reference.

In your shared_value scheme, a modification to one shared_value<T> can be observed by another shared_value with the same target. Can you prevent a shared_value<T const> having the same target as a shared_value<T>?

Nicol Bolas

unread,
Aug 25, 2016, 10:10:55 AM8/25/16
to ISO C++ Standard - Future Proposals
On Thursday, August 25, 2016 at 3:29:28 AM UTC-4, Edward Catmur wrote:

On 25 Aug 2016 1:54 a.m., "Nicol Bolas" <jmck...@gmail.com> wrote:
>
> On Wednesday, August 24, 2016 at 7:57:33 PM UTC-4, Edward Catmur wrote:
>>
>> On Wednesday, 24 August 2016 15:45:12 UTC+1, Nicol Bolas  wrote:
>> > On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
>> >
>> > There is an interesting concept of pointers pair: const_shared with mutable_unique. 
>> >
>> > Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
>> >
>> > Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.
>>
>> But there can be an already existing shared_ptr<T> aliasing your shared_ptr<T const>.
>
>
> Without a `const_cast` or similar? If you do `make_shared<const T>(...)`, I have no idea how you would get the aliasing to `shared_ptr<T>` without a `const_cast` or similar.
>
> At least, not unless it's a pointer to a different `T`.

If you do make_shared<T>(...) then you can get a shared_ptr<T const> later that has the same target as the original shared_ptr<T>.


I fail to see your point.

My point is that `shared_ptr` gives a user all the tools that `const_shared_ptr` gives the user for making a non-modifiable shared pointer to T. Namely, `shared_ptr<const T>`. If you create it `const` initially, then nobody can take that away without an explicit `const_cast` or similar construct.

This, in regards to the const-ness of `T`, `shared_ptr` is just as guaranteed as the proposed `const_shared_ptr`. Indeed, you could do this:

template<typename T>
using const_shared_ptr = std::shared_ptr<std::add_const_t<T>>;

template<typename T, template ...Args>
auto make_const_shared(Args &&...args) { return std::make_shared<std::add_const_t<T>>(std::forward<Args>(args)...); }

And you get the same `const` protection as the proposed `const_shared_ptr`, so long as that is how you create them initially.

>> > Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>> >
>> > So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.
>>
>> Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".
>
>
> Conceptually, it's a value type, so it wouldn't have aliasing of any form. What would that even mean? Would you be aliasing a value with another value? Indeed, the OP excluded aliasing from `const_shared_ptr`'s interface, since it doesn't make sense.

Sorry, I didn't mean aliasing in the sense of creating a shared_ptr to a subobject, rather in the general sense of one pointer having the same target as another pointer, or one reference having the same referent as another reference.

In your shared_value scheme, a modification to one shared_value<T> can be observed by another shared_value with the same target. Can you prevent a shared_value<T const> having the same target as a shared_value<T>?


The same way as I showed for `shared_ptr`: make it `const` when you create the first one.

If you want pure COW semantics from the type I've suggested, then you make a `shared_value<const T>` at the point of origin. At no time do you make a `shared_value<T>`.

The thing is, I've always thought we should have a `value_ptr<T>` type. That is, a type that *certainly* allocates memory for `T`, moves via moving the pointer, but copies itself by copying the object by value. This would be useful for cases where `T` is expensive to move and/or throws on moving.

A `shared_value_ptr<T>` would not be an unreasonable pairing for such a type. And then we could have `shared_value_ptr<const T>` serve the needs the OP requires for COW gymnastics, so long as there is a conversion from `shared_value_ptr` to `value_ptr` which is permitted to avoid a copy if the `shared_value_ptr` moved into it is the last one.

Edward Catmur

unread,
Aug 25, 2016, 11:27:43 AM8/25/16
to std-pr...@isocpp.org
On Thu, Aug 25, 2016 at 3:10 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, August 25, 2016 at 3:29:28 AM UTC-4, Edward Catmur wrote:

On 25 Aug 2016 1:54 a.m., "Nicol Bolas" <jmck...@gmail.com> wrote:
>
> On Wednesday, August 24, 2016 at 7:57:33 PM UTC-4, Edward Catmur wrote:
>>
>> On Wednesday, 24 August 2016 15:45:12 UTC+1, Nicol Bolas  wrote:
>> > On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
>> >
>> > There is an interesting concept of pointers pair: const_shared with mutable_unique. 
>> >
>> > Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
>> >
>> > Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.
>>
>> But there can be an already existing shared_ptr<T> aliasing your shared_ptr<T const>.
>
>
> Without a `const_cast` or similar? If you do `make_shared<const T>(...)`, I have no idea how you would get the aliasing to `shared_ptr<T>` without a `const_cast` or similar.
>
> At least, not unless it's a pointer to a different `T`.

If you do make_shared<T>(...) then you can get a shared_ptr<T const> later that has the same target as the original shared_ptr<T>.


I fail to see your point.

My point is that `shared_ptr` gives a user all the tools that `const_shared_ptr` gives the user for making a non-modifiable shared pointer to T. Namely, `shared_ptr<const T>`. If you create it `const` initially, then nobody can take that away without an explicit `const_cast` or similar construct.

This, in regards to the const-ness of `T`, `shared_ptr` is just as guaranteed as the proposed `const_shared_ptr`. Indeed, you could do this:

template<typename T>
using const_shared_ptr = std::shared_ptr<std::add_const_t<T>>;

template<typename T, template ...Args>
auto make_const_shared(Args &&...args) { return std::make_shared<std::add_const_t<T>>(std::forward<Args>(args)...); }

And you get the same `const` protection as the proposed `const_shared_ptr`, so long as that is how you create them initially.

Right, but only if you control all the locations where the shared_ptr<T [const]> is created and will be created in the future. If someone hands you a shared_ptr<T const> it might have been created by make_shared<T const> but it might also have been created initially as a make_shared<T>, so you can't be sure that no-one has a mutable handle to the object you're accessing.

>> > Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>> >
>> > So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.
>>
>> Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".
>
>
> Conceptually, it's a value type, so it wouldn't have aliasing of any form. What would that even mean? Would you be aliasing a value with another value? Indeed, the OP excluded aliasing from `const_shared_ptr`'s interface, since it doesn't make sense.

Sorry, I didn't mean aliasing in the sense of creating a shared_ptr to a subobject, rather in the general sense of one pointer having the same target as another pointer, or one reference having the same referent as another reference.

In your shared_value scheme, a modification to one shared_value<T> can be observed by another shared_value with the same target. Can you prevent a shared_value<T const> having the same target as a shared_value<T>?


The same way as I showed for `shared_ptr`: make it `const` when you create the first one.

If you want pure COW semantics from the type I've suggested, then you make a `shared_value<const T>` at the point of origin. At no time do you make a `shared_value<T>`.

But then you can *never* modify it; you can only copy it to a new mutable value. Which is great for functional purity, but could also be inefficient.

The thing is, I've always thought we should have a `value_ptr<T>` type. That is, a type that *certainly* allocates memory for `T`, moves via moving the pointer, but copies itself by copying the object by value. This would be useful for cases where `T` is expensive to move and/or throws on moving.

A `shared_value_ptr<T>` would not be an unreasonable pairing for such a type. And then we could have `shared_value_ptr<const T>` serve the needs the OP requires for COW gymnastics, so long as there is a conversion from `shared_value_ptr` to `value_ptr` which is permitted to avoid a copy if the `shared_value_ptr` moved into it is the last one.

But that conversion would give you a value_ptr<T const>, so it would still be immutable even when it should be safe to mutate.

 

Nicol Bolas

unread,
Aug 25, 2016, 3:37:40 PM8/25/16
to ISO C++ Standard - Future Proposals


On Thursday, August 25, 2016 at 11:27:43 AM UTC-4, Edward Catmur wrote:
On Thu, Aug 25, 2016 at 3:10 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, August 25, 2016 at 3:29:28 AM UTC-4, Edward Catmur wrote:

On 25 Aug 2016 1:54 a.m., "Nicol Bolas" <jmck...@gmail.com> wrote:
>
> On Wednesday, August 24, 2016 at 7:57:33 PM UTC-4, Edward Catmur wrote:
>>
>> On Wednesday, 24 August 2016 15:45:12 UTC+1, Nicol Bolas  wrote:
>> > On Wednesday, August 24, 2016 at 3:39:33 AM UTC-4, Alexey Mamontov wrote:
>> >
>> > There is an interesting concept of pointers pair: const_shared with mutable_unique. 
>> >
>> > Const shared is move-constructible from mutable_unique: this enqures that the value we have a const shared pointer to can not be references for edit from any other location (the problem that shared_ptr<const T> can't solve).
>> >
>> > Why does it not solve this problem? Outside of `const_pointer_cast` or similar gymnastics, if you have a `shared_ptr<const T>`, you cannot convert it into a `shared_ptr<T>`. Oh sure, you can always `get` the pointer and `const_cast` it. But your `const_shared_ptr` has no way to prevent that either.
>>
>> But there can be an already existing shared_ptr<T> aliasing your shared_ptr<T const>.
>
>
> Without a `const_cast` or similar? If you do `make_shared<const T>(...)`, I have no idea how you would get the aliasing to `shared_ptr<T>` without a `const_cast` or similar.
>
> At least, not unless it's a pointer to a different `T`.

If you do make_shared<T>(...) then you can get a shared_ptr<T const> later that has the same target as the original shared_ptr<T>.


I fail to see your point.

My point is that `shared_ptr` gives a user all the tools that `const_shared_ptr` gives the user for making a non-modifiable shared pointer to T. Namely, `shared_ptr<const T>`. If you create it `const` initially, then nobody can take that away without an explicit `const_cast` or similar construct.

This, in regards to the const-ness of `T`, `shared_ptr` is just as guaranteed as the proposed `const_shared_ptr`. Indeed, you could do this:

template<typename T>
using const_shared_ptr = std::shared_ptr<std::add_const_t<T>>;

template<typename T, template ...Args>
auto make_const_shared(Args &&...args) { return std::make_shared<std::add_const_t<T>>(std::forward<Args>(args)...); }

And you get the same `const` protection as the proposed `const_shared_ptr`, so long as that is how you create them initially.

Right, but only if you control all the locations where the shared_ptr<T [const]> is created and will be created in the future. If someone hands you a shared_ptr<T const> it might have been created by make_shared<T const> but it might also have been created initially as a make_shared<T>, so you can't be sure that no-one has a mutable handle to the object you're accessing.

If you don't control those locations, how can you possibly force them to use `const_shared_ptr`?

Furthermore, the OP makes it clear that you can change the value of `T` within a `const_shared_ptr`. In the OP's design, you can pass a non-const T* to `const_shared_ptr`'s constructor. You can still use that non-`const` pointer to modify the object's state. If the OP were interested in perfect const-ness, then the API would only be accessible via an emplacement constructor/make_const_shared_ptr, either of which would prevent someone from having a non-const pointer without an explicit `const_cast`.

So if the goal of this design is to ensure `const`-ness of the object at all times (outside of casts), then the given design is flawed.

>> > Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>> >
>> > So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.
>>
>> Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".
>
>
> Conceptually, it's a value type, so it wouldn't have aliasing of any form. What would that even mean? Would you be aliasing a value with another value? Indeed, the OP excluded aliasing from `const_shared_ptr`'s interface, since it doesn't make sense.

Sorry, I didn't mean aliasing in the sense of creating a shared_ptr to a subobject, rather in the general sense of one pointer having the same target as another pointer, or one reference having the same referent as another reference.

In your shared_value scheme, a modification to one shared_value<T> can be observed by another shared_value with the same target. Can you prevent a shared_value<T const> having the same target as a shared_value<T>?


The same way as I showed for `shared_ptr`: make it `const` when you create the first one.

If you want pure COW semantics from the type I've suggested, then you make a `shared_value<const T>` at the point of origin. At no time do you make a `shared_value<T>`.

But then you can *never* modify it; you can only copy it to a new mutable value. Which is great for functional purity, but could also be inefficient.

That's what the OP asked for. You start with `const_shared_ptr` and hand it out to people who can make mutable copies of it.

The thing is, I've always thought we should have a `value_ptr<T>` type. That is, a type that *certainly* allocates memory for `T`, moves via moving the pointer, but copies itself by copying the object by value. This would be useful for cases where `T` is expensive to move and/or throws on moving.

A `shared_value_ptr<T>` would not be an unreasonable pairing for such a type. And then we could have `shared_value_ptr<const T>` serve the needs the OP requires for COW gymnastics, so long as there is a conversion from `shared_value_ptr` to `value_ptr` which is permitted to avoid a copy if the `shared_value_ptr` moved into it is the last one.

But that conversion would give you a value_ptr<T const>, so it would still be immutable even when it should be safe to mutate.

This is perfectly legitimate code:

const T t = ...;
T t2
= std::move(t);

Why should this not be legitimate:

shared_value_ptr<const T> t = ...;
value_ptr
<T> t2 = std::move(t);

The only difference between the two is that the last one can not copy the object if `t` is the last owner of that memory.

Edward Catmur

unread,
Aug 25, 2016, 7:05:37 PM8/25/16
to std-pr...@isocpp.org

Well, if they want to call your code they're going to have to create a const_shared_ptr at some point.

> Furthermore, the OP makes it clear that you can change the value of `T` within a `const_shared_ptr`. In the OP's design, you can pass a non-const T* to `const_shared_ptr`'s constructor. You can still use that non-`const` pointer to modify the object's state. If the OP were interested in perfect const-ness, then the API would only be accessible via an emplacement constructor/make_const_shared_ptr, either of which would prevent someone from having a non-const pointer without an explicit `const_cast`.
>
> So if the goal of this design is to ensure `const`-ness of the object at all times (outside of casts), then the given design is flawed.

Ah, I missed that bit, thanks. Still, an API that promotes correctness but can be subverted is still better than an API that requires constant vigilance to use safely.

>>>> >> > Indeed, I would even go so far as to say that, if this is the idea, then giving it an existing object to govern is really the wrong interface. Such a type should create the objects it manages internally; this would allow the implementation to be more efficient (ala `make_shared`). That is, you construct the mutable unique value type, then move the value into your "const" COW shared object.
>>>> >> >
>>>> >> > So what you really have is `unique_value<T>` and `shared_value<const T>`, with conversions between them. This also permits you to have a mutable `shared_value<T>` if a user wants it, rather than enforcing immutability at the interface level.
>>>> >>
>>>> >> Would that allow enforcement of non modification by an aliasing value? There's two sides to immutability; there's "I can't modify it" and there's "no-one can modify it".
>>>> >
>>>> >
>>>> > Conceptually, it's a value type, so it wouldn't have aliasing of any form. What would that even mean? Would you be aliasing a value with another value? Indeed, the OP excluded aliasing from `const_shared_ptr`'s interface, since it doesn't make sense.
>>>>
>>>> Sorry, I didn't mean aliasing in the sense of creating a shared_ptr to a subobject, rather in the general sense of one pointer having the same target as another pointer, or one reference having the same referent as another reference.
>>>>
>>>> In your shared_value scheme, a modification to one shared_value<T> can be observed by another shared_value with the same target. Can you prevent a shared_value<T const> having the same target as a shared_value<T>?
>>>
>>>
>>> The same way as I showed for `shared_ptr`: make it `const` when you create the first one.
>>>
>>> If you want pure COW semantics from the type I've suggested, then you make a `shared_value<const T>` at the point of origin. At no time do you make a `shared_value<T>`.
>>
>>
>> But then you can *never* modify it; you can only copy it to a new mutable value. Which is great for functional purity, but could also be inefficient.
>
>
> That's what the OP asked for. You start with `const_shared_ptr` and hand it out to people who can make mutable copies of it.

Yes, but OP also wants to be able to convert it back to mutable form if that can be done safely.

>>> The thing is, I've always thought we should have a `value_ptr<T>` type. That is, a type that *certainly* allocates memory for `T`, moves via moving the pointer, but copies itself by copying the object by value. This would be useful for cases where `T` is expensive to move and/or throws on moving.
>>>
>>> A `shared_value_ptr<T>` would not be an unreasonable pairing for such a type. And then we could have `shared_value_ptr<const T>` serve the needs the OP requires for COW gymnastics, so long as there is a conversion from `shared_value_ptr` to `value_ptr` which is permitted to avoid a copy if the `shared_value_ptr` moved into it is the last one.
>>
>>
>> But that conversion would give you a value_ptr<T const>, so it would still be immutable even when it should be safe to mutate.
>
>
> This is perfectly legitimate code:
>
> const T t = ...;
> T t2 = std::move(t);

Legitimate, but it has unexpected semantics; it doesn't call the move constructor.

> Why should this not be legitimate:
>
> shared_value_ptr<const T> t = ...;
> value_ptr<T> t2 = std::move(t);
>
> The only difference between the two is that the last one can not copy the object if `t` is the last owner of that memory.

Well, it wouldn't work if the constructor of shared_value_ptr<T const> ends up calling new T const, because the pointee would be an actually const object.

The implementation would have to remove_const on T before constructing the pointee, which would inhibit optimization if you didn't intend ever to use it mutably.

Reply all
Reply to author
Forward
0 new messages