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.
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?
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>?
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>.
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)...); }
>> > 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>?
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.
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.
const T t = ...;
T t2 = std::move(t);
shared_value_ptr<const T> t = ...;
value_ptr<T> t2 = std::move(t);
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.