class container {
public:
int const* get_value() const { return value; }
int* get_value() { return value; }
private:
propagate_const<int*> value;
};
class container {
public:
shared_ptr<int const> get_value() const { return value; } // error: cannot convert...
shared_ptr<int> get_value() { return value; } // error: cannot convert...
private:
propagate_const<shared_ptr<int*>> value;
};
This is because propagate_const<T> can only implicitly convert to element_type const* and element_type*; propagate_const<T> should support implicit conversion to T and whatever the const version of T is. Of course, it makes sense for propagate_const<T> to also implicitly convert to element_type const* and element_type* if T is implicitly convertible to element_type const* and element_type* (which shared_ptr<T> isn't).class container {
public:
shared_ptr<int> get_value() const { return get_underlying(value); } // const-correctness lost
shared_ptr<int> get_value() { return get_underlying(value); }
private:
propagate_const<shared_ptr<int>> value;
};
operator T&();
operator const_version_t<T>(); // sample implementation
Another suggestion I anticipate is that this is what get_underlying is for. But get_underlying does break const-correctness. Case in point, this compiles:
class container {
public:
shared_ptr<int> get_value() const { return get_underlying(value); } // const-correctness lost
shared_ptr<int> get_value() { return get_underlying(value); }
private:
propagate_const<shared_ptr<int>> value;
};
class container {
public:
shared_ptr<const int> get_value() const { return get_underlying(value); } // const-correctness RESTORED
shared_ptr<int> get_value() { return get_underlying(value); }
private:
propagate_const<shared_ptr<int>> value;
};
propagate_const<std::shared_ptr<X>> get_value() const { return get_underlying(m_ptrX); }
const auto p = c.get_value();
p->func(); //< calls a void func() const overload
On Thursday, October 20, 2016 at 11:33:28 AM UTC+3, joseph....@gmail.com wrote:...
Another suggestion I anticipate is that this is what get_underlying is for. But get_underlying does break const-correctness. Case in point, this compiles:
class container {
public:
shared_ptr<int> get_value() const { return get_underlying(value); } // const-correctness lost
shared_ptr<int> get_value() { return get_underlying(value); }
private:
propagate_const<shared_ptr<int>> value;
};This is however correct:
class container {
public:
shared_ptr<const int> get_value() const { return get_underlying(value); } // const-correctness RESTORED
shared_ptr<int> get_value() { return get_underlying(value); }
private:
propagate_const<shared_ptr<int>> value;
};If you get_value on a const container; it will return to you the const-correct internal pointer.
class container {
public:
int* get_value() const { return value; } // error: cannot convert...
int* get_value() { return value; }
private:
propagate_const<int*> value;
};
class container {
public:
int* get_value() const { return get_underlying(value); }
int* get_value() { return value; }
private:
propagate_const<int*> value;
};
And I think the key here is internal.I think the main purpose of propagate is to be an implementation helper an you are letting the pointer escape.If you are letting the pointer escape you can also do
propagate_const<std::shared_ptr<X>> get_value() const { return get_underlying(m_ptrX); }This way it is up to caller to decide if the return value is const or not, enabling this usage.
const auto p = c.get_value();
p->func(); //< calls a void func() const overload
Of course, makes sense.The constness we care about is the one of the instance, we can't communicate and "transfer" it on creation or assignment to a new instance.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/60ae1a96-7a31-491d-bcf8-c81590544006%40isocpp.org.
Adding `const_type` so that `const propagate_const<T>` is implicitly convertible to `const_type_t<T>` seems pretty appealing to me.
...
template<class T>
using AutoConstT [[inherit_const]] = T; //< T will be const when it is accessed from a const pointer
struct C {
AutoConstT<T>* p;
};
const C c;
c.p->func(); //< c is const => p is const => T is const
const std::shared_ptr<AutoConstT<T>> p = getP(); //< AutoConstT will tunnel down to shared_ptr's _Tp* _M_ptr; // Contained pointer.
p->func(); //< const overload
auto p2 = p; //< not ok
const auto p3 = p; //< ok
The shared_ptr will still be uncopyable, because its copy operations take the object to copy by reference to const. To allow copying of propagate_const or types that use it would require formalising a non-const copyable concept. You can define non-const copy operations, and the compiler does appear to automatically generate non-const operations if const versions aren't possible, but it isn't something the standard library or language supports particularly well. In fact, it isnt possible to define both const and non-const copy operations simultaneously.
...
struct S
{
S() = default;
S& operator=(const S&) { std::cout << "=const S&" ; return *this; }
S& operator=(S&) { std::cout << "=S&" ; return *this; }
};
int main()
{
const S s;
S ss;
S sss;
ss = s; //< "=const S&"
sss = ss; //< "=S&"
}
Adding `const_type` so that `const propagate_const<T>` is implicitly convertible to `const_type_t<T>` seems pretty appealing to me.`get_underlying` is very much like a cast operation, it was added so the user can discard const_propagation when required.
template <typename T>
T const& get_underlying(propagate_const<T> const& pc) {
return const_cast<propagate_const<T>&>(pc);
}
template <typename T>
T& get_underlying(propagate_const<T>& pc) {
return pc;
}
BTW, what is your use case? Or you meant from purely correctness point of view?
class container {
public:
observer_ptr<int const> get_value() const { return make_observer(value.get()); }
observer_ptr<int> get_value() { return make_observer(value.get()); }
private:
propagate_const<observer_ptr<int>> value;
};
On Saturday, 22 October 2016 16:34:54 UTC+8, mihailn...@gmail.com wrote:BTW, what is your use case? Or you meant from purely correctness point of view?
My personal use case is std::observer_ptr and the like. You can work around the limitations of propagate_const with these non-owning wrappers; it's just a bit of a hassle:
class container {
public:
observer_ptr<int const> get_value() const { return make_observer(value.get()); }
observer_ptr<int> get_value() { return make_observer(value.get()); }
private:
propagate_const<observer_ptr<int>> value;
};
...
observer_ptr<int const> get_cvalue() const;
container c;
auto v = std::as_const(c).get_value();
observer_ptr<int const> v = c.get_value();
const auto v = c.get_value();
On Saturday, October 22, 2016 at 1:02:43 PM UTC+3, joseph....@gmail.com wrote:On Saturday, 22 October 2016 16:34:54 UTC+8, mihailn...@gmail.com wrote:BTW, what is your use case? Or you meant from purely correctness point of view?
My personal use case is std::observer_ptr and the like. You can work around the limitations of propagate_const with these non-owning wrappers; it's just a bit of a hassle:...
class container {
public:
observer_ptr<int const> get_value() const { return make_observer(value.get()); }
observer_ptr<int> get_value() { return make_observer(value.get()); }
private:
propagate_const<observer_ptr<int>> value;
};I see.Too bad we'll end up yet again with duplicated const and non conts interfaces.For instance in your case there also should be a
observer_ptr<int const> get_cvalue() const;Because the user might want to get a const observer from a non-const instance of the container.Or do a bit awkward things like
container c;auto v = std::as_const(c).get_value();or manually specify a different type
observer_ptr<int const> v = c.get_value();No matter which of the 3 one thinks is right, the fact there so much "correct" ways is not a good thing.
Your comments on the other thread really made me think, an observer ptr should be able propagate const.This will enable the correct default for objects to be the correct default for handles-to-objects as well
const auto v = c.get_value();Sadly, we can't really have that right now :(
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/a51760a2-b976-4ef6-9214-bba82af80b79%40isocpp.org.
On Saturday, 22 October 2016 21:08:50 UTC+8, mihailn...@gmail.com wrote:
...
Const-propagation only really has value in cases of composition, where you want to ensure that, when an object is only accessible via a reference to const, that the constness propagates not only to its data members, but also any objects those data members indirectly reference. If we restrict our use of propagate_const to the data members of a class, then we get natural copying syntax, and callers have the flexibility to do with their copies what they want.
struct S
{
propagate_const<shared_ptr<int>> p;
};
struct S
{
S() = default;
S(S&&) = default;
S& operator=(S&&) = default;
S(const S& o) : _p(get_underlying(o._p)) {}
S& operator=(const S& o) { if(&o != this) { _p = get_underlying(o._p); } return *this; };
propagate_const<shared_ptr<int>> _p;
};
The object relationship being modelled by a class with a shared pointer member which is externally visible as a shared pointer (it is returned as a shared_ptr<T> or shared_ptr<const T>) is a bit unclear to me.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
container(container& other) :
value(other.value) {
}
container& operator=(container& other) {
value = other.value;
return *this;
}
Deep copying wouldn't be a problem. I'm just having a hard time figuring out what exactly a deep-copying indirect type would do or how it would be useful. I guess I'll have to wait for the details of this std::indirect.
struct S
{
S() : _p(std::make_unique<X>()) {}
S(S&&) = default;
S& operator=(S&&) = default;
S(const S& o) : _p(std::make_unique<X>(*o._p)) {}
S& operator=(const S& o) { if(&o != this) { _p = std::make_unique<X>(*o._p); } return *this; };
propagate_const<std::unique_ptr<X>> _p;
};
From: mihailn...@gmail.com Sent: Sunday, October 23, 2016 12:00 PM To: ISO C++ Standard - Future Proposals Reply To: std-pr...@isocpp.org Subject: Re: [std-proposals] Re: [propagate_const] Implicit conversion to T |
I would say propagate_const *implies* ownership which implies deep-copy. (or no copy).I would be suspect of situations that use propagate_const but don't have ownership semantics.
From: joseph....@gmail.com Sent: Sunday, October 23, 2016 1:52 PM |
Yes, value semantics is more accurate.Sent from my BlackBerry portable Babbage Device
From: joseph....@gmail.comSent: Sunday, October 23, 2016 1:52 PMTo: ISO C++ Standard - Future ProposalsReply To: std-pr...@isocpp.orgSubject: Re: [std-proposals] Re: [propagate_const] Implicit conversion to TOn Monday, 24 October 2016 00:10:06 UTC+8, Tony V E wrote:I would say propagate_const *implies* ownership which implies deep-copy. (or no copy).
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/20161023175710.4911185.4764.19015%40gmail.com.
...
As promised, here is 'indirect' which gives const-propagation and deep-copy.
..
On Monday, October 24, 2016 at 9:40:59 AM UTC+3, Jonathan Coe wrote:...As promised, here is 'indirect' which gives const-propagation and deep-copy...This is indeed a handy tool - we have all written at least one "poor man's" version of it in our code.However I am worried people will use it even if cheaper alternatives are sufficient, just because it is easier.The type-erasing control block is a price people will pay even if they don't have to, and without being aware of it.Some other comments:- I believe the move-ctor and move-assignment (line 193 and 254) cannot be noexcept - they create a new control block.
And the fact that move-ops are not non-throwing is a considerable drawback.
- I am not a fan of the name indirect<> - it is very vague.May I suggest deep_ptr, or even deep_copy_ptr, because in the end it does model a pointer to object and it is about its lifetime.It is an extension of pointers, not on basic vocabulary types. Where shared_ptr and unique_ptr handle new and delete only, this one adds copy as well.
Basic vocabulary types are created from values even if they alloc and manage mem internally, like vector.This one is created from a pointer - thus it a pointer management class, not a basic type.- Again, I think it is handily tool, but easily overused - it makes you not think about copies of big objects, which will lead to too may of them.Not unlike how std::vector and std::string are used by unexperienced programmers or ones coming from a different language like Java or C#.Everything will be an indirect<> to solve all memory management discomforts.Value sematic are great, but low number of copies is also great. I am afraid this will lead to explosion (hidden) copies of big objects in user code.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f88b96df-e3b2-4845-98d9-05c216cf47c3%40isocpp.org.
On 24 October 2016 at 10:10, <mihailn...@gmail.com> wrote:
On Monday, October 24, 2016 at 9:40:59 AM UTC+3, Jonathan Coe wrote:...As promised, here is 'indirect' which gives const-propagation and deep-copy...This is indeed a handy tool - we have all written at least one "poor man's" version of it in our code.However I am worried people will use it even if cheaper alternatives are sufficient, just because it is easier.The type-erasing control block is a price people will pay even if they don't have to, and without being aware of it.
...
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f88b96df-e3b2-4845-98d9-05c216cf47c3%40isocpp.org.
I suggest the following:
This results in a value-like type that is much more consistent in its behaviour, and is potentially slightly more efficient in space and time. I have pushed a working sample implementation to a fork of your repository. Note that it is more inspired by your original implementation than an evolution of it, and the tests aren't complete by any means.
As for the name, I'm not a huge fan of std::indirect, because it very much says "pointer" to me. Perhaps something like std::polymorphic would be better.
> On 26 Oct 2016, at 20:13, Ville Voutilainen <ville.vo...@gmail.com> wrote:
>
> On 26 October 2016 at 22:11, <joseph....@gmail.com> wrote:
>>> Being able to allow the user to adapt a non-copyable type to be cloned
>>> by indirect,
>>> and being able to use indirect instead of a hand-written solution that can
>>> clone
>>> polymorphic types via pointers, with the ability to adopt a pointer
>>> directly without
>>> needing an extra clone. Basically, to do most of the things a pointer can
>>> do,
>>> with the cloning ability.
>>
>>
>> Neither of these use cases is stated in the proposal.
>
> Yet indirect<T> supports those use cases fine, because their
> motivation comes from
> practical experience.
>
>> And if the type is
>> meant to basically be a copyable unique_ptr, then it probably needs to go
>> back to being called cloned_ptr and embrace its status as a
>> resource-managing pointer-like type. The proposed behaviour of indirect
>> isn't really very value-like; it's a weird mix of value-like and
>> pointer-like.
>
Which bits are 'pointer-like'?
It's more value-like than it was but may have further to go.
T t; // t default constructed (value-like)
polymorphic<T> p; // *p default constructed (value-like)
indirect<T> i; // i is empty (pointer-like)
T t = tt; // t copy constructed (value-like)
polymorphic<T> p = tt; // *p copy constructed (value-like)
indirect<T> i = tt; // error: cannot assign from T (pointer-like)
T t = std::move(tt); // t move constructed; tt moved from (value-like)
polymorphic<T> p = std::move(pp); // *p move constructed; *pp moved from (value-like)
indirect<T> i = std::move(ii); // ownership transferred from ii to i; ii now empty (pointer-like)
T t{1, 2, 3}; // t constructed in-place with arguments (value-like)
polymorphic<T> p{in_place, 1, 2, 3}; // *p constructed in-place with arguments (value-like)
indirect<T> i{new T{1, 2, 3}}; // i constructed from pointer that it takes ownership of (pointer-like)
// note: operator bool not defined for T
if (t) { … } // error: t cannot convert to bool (value-like)
if (p) { … } // error: p cannot convert to bool (value-like)
if (i) { … } // i checked for empty status (pointer-like)
...
T t; // t default constructed (value-like)
polymorphic<T> p; // *p default constructed (value-like)
indirect<T> i; // i is empty (pointer-like)
T t = tt; // t copy constructed (value-like)
polymorphic<T> p = tt; // *p copy constructed (value-like)
indirect<T> i = tt; // error: cannot assign from T (pointer-like)
T t = std::move(tt); // t move constructed; tt moved from (value-like)
polymorphic<T> p = std::move(pp); // *p move constructed; *pp moved from (value-like)
indirect<T> i = std::move(ii); // ownership transferred from ii to i; ii now empty (pointer-like)
T t{1, 2, 3}; // t constructed in-place with arguments (value-like)
polymorphic<T> p{in_place, 1, 2, 3}; // *p constructed in-place with arguments (value-like)
indirect<T> i{new T{1, 2, 3}}; // i constructed from pointer that it takes ownership of (pointer-like)
// note: operator bool not defined for T
if (t) { … } // error: t cannot convert to bool (value-like)
if (p) { … } // error: p cannot convert to bool (value-like)
if (i) { … } // i checked for empty status (pointer-like)
...
T t; // t default constructed (value-like)
polymorphic<T> p; // *p default constructed (value-like)
indirect<T> i; // i is empty (pointer-like)
T t = tt; // t copy constructed (value-like)
polymorphic<T> p = tt; // *p copy constructed (value-like)
indirect<T> i = tt; // error: cannot assign from T (pointer-like)
T t = std::move(tt); // t move constructed; tt moved from (value-like)
polymorphic<T> p = std::move(pp); // *p move constructed; *pp moved from (value-like)
indirect<T> i = std::move(ii); // ownership transferred from ii to i; ii now empty (pointer-like)
T t{1, 2, 3}; // t constructed in-place with arguments (value-like)
polymorphic<T> p{in_place, 1, 2, 3}; // *p constructed in-place with arguments (value-like)
indirect<T> i{new T{1, 2, 3}}; // i constructed from pointer that it takes ownership of (pointer-like)
// note: operator bool not defined for T
if (t) { … } // error: t cannot convert to bool (value-like)
if (p) { … } // error: p cannot convert to bool (value-like)
if (i) { … } // i checked for empty status (pointer-like)I agree it is more close to pointer then to a value, but that's a good thing. It should feel, you are managing a remote recourse.
Also, note that indirect<>, is intended to be constructed with make_indirect NOT with the ctor with a pointer.
About polymorphicThe way it is right now, you do many allocations, just to pretend it is a real variable on the stack - allocating default ctor, not empty after move, etc.All these I don't think are of benefit compared to the cost.
Assignment from raw value however might be useful, especially move assignment.
Lastly, I find custom copier and deleter to be essential for the class to be useful.
Yes, I think the need for being able to supply a copier and deleter defeats any attempt to make an "emplace style" constructor. How can we know where each parameter is to go?
For the original proposal it seems that make_indirect() would also need these optional parameters so it would end up in the same bad place... the only way I can think of to handle this would be to make four different function names. What am I missing?
Copier and deleter do not make sense for a value-like type.