[propagate_const] Implicit conversion to T

280 views
Skip to first unread message

joseph....@gmail.com

unread,
Oct 20, 2016, 4:33:28 AM10/20/16
to ISO C++ Standard - Future Proposals
The idea of the proposed propagate_const class template is growing on me, but it has a few issues, one of which I wish to address here, and one of which I will raise in another thread.

propagate_const is great for storing raw pointers; take the following example code:

class container {
public:
 
int const* get_value() const { return value; }
  int* get_value() { return value; }

private
:
  propagate_const
<int*> value;
};

The use of propagate_const has effectively enforced const-correctness on container: we cannot return value as an int* from the const overload of get_value. However, if you replace int* with shared_ptr<int> then there is a problem:

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).

One complaint I can imagine is that enabling such a conversion breaks the const-correctness enforced by propagate_const. This is not true; at least, it is no more true to say that allowing implicit conversion from propagate_const<shared_ptr<T>> to shared_ptr<T> breaks const-correctness than it is to say that allowing implicit conversion from propagate_const<T*> to T* breaks const-correctness. Bottom line is, propagate_const simply ensures that container's implementation is const-correct; it's job isn't to propagate constness outside of the API boundaries.

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;
};

The current design of propagate_const renders it ineffective for use with types such as shared_ptr<T>. I believe the solution is to add implicit conversion operators to T& and the const version of T; for example:

  operator T&();
 
operator const_version_t<T>(); // sample implementation

Note that the second conversion operator will only exist if const_version_t<T> is not the same as T.

Determining the const version of T* is easy (it's T const*), but how should it be determined for an arbitrary type? I suggest adding one more requirement on T: the existence of a T::const_type member type alias. This can easily be added to the standard library smart pointers (like weak_type was just added to shared_ptr in C++17). Using a specializable type trait struct seems like overkill to me, but it's another possibility.

Let me know you thoughts, and please tell me if I've missed something obvious so that I can slink into the corner feeling very stupid.

mihailn...@gmail.com

unread,
Oct 20, 2016, 5:33:32 AM10/20/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com


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.

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


joseph....@gmail.com

unread,
Oct 20, 2016, 11:29:03 AM10/20/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com, mihailn...@gmail.com
On Thursday, 20 October 2016 17:33:32 UTC+8, mihailn...@gmail.com wrote:


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.
 
My point is that you can write code that violates the restrictions that are meant to be enforced by propagate_const. For example, this code will not compile:

class container {
public:
 
int* get_value() const { return value; } // error: cannot convert...

 
int* get_value() { return value; }

private:
  propagate_const
<int*> value;
};

But this code will:

class container {
public:
 
int* get_value() const { return get_underlying(value); }

 
int* get_value() { return value; }

private:
  propagate_const
<int*> value;
};

get_underlying is essentially const_cast for propagate_const; you should not be using it on a regular basis, but there is currently no other option when using propagate_const with shared_ptr (which makes this combination practically useless), hence my suggested adjustments.

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

 
This is certainly not a usage pattern I envisaged with propagate_const. Use of get_underlying defeats the point of using propagate_const in the first place, and use of propagate_const in the API seems wrong; as you said, it is intended as an implementation helper.

mihailn...@gmail.com

unread,
Oct 20, 2016, 12:40:12 PM10/20/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com, mihailn...@gmail.com
To be honest, this tool is too new to have a strong opinion.

For instance I am not entirely sure why it forbids copy no matter the type it wraps. This was the reason get_underlying must be used to create new instance of propagate_const to return to the user. Shouldn't it have the same copy capability as the underlying type?

Otherwise, I see the problem. It seems it only provides implicate conversion if underlying is a raw pointer. Probably because, as you said, the constness is well defined.

Ville Voutilainen

unread,
Oct 20, 2016, 12:45:51 PM10/20/16
to ISO C++ Standard - Future Proposals
On 20 October 2016 at 19:40, <mihailn...@gmail.com> wrote:
> For instance I am not entirely sure why it forbids copy no matter the type
> it wraps. This was the reason get_underlying must be used to create new
> instance of propagate_const to return to the user. Shouldn't it have the
> same copy capability as the underlying type?

No, because e.g. for a raw pointer as the underlying type, you can
copy a const pointer to a non-const pointer
every bit as much as you can copy a const int to a non-const int.
Moving is fine because it's fair to assume
that the original const isn't used by anything. The same logic applies
to shared_ptr as the underlying type.

mihailn...@gmail.com

unread,
Oct 20, 2016, 4:53:49 PM10/20/16
to ISO C++ Standard - Future Proposals
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.

Jonathan Coe

unread,
Oct 20, 2016, 5:08:54 PM10/20/16
to std-pr...@isocpp.org
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.

On 20 October 2016 at 21:53, <mihailn...@gmail.com> wrote:
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.

mihailn...@gmail.com

unread,
Oct 21, 2016, 4:36:10 AM10/21/16
to ISO C++ Standard - Future Proposals, jb...@me.com


On Friday, October 21, 2016 at 12:08:54 AM UTC+3, Jonathan Coe wrote:
Adding `const_type` so that `const propagate_const<T>` is implicitly convertible to `const_type_t<T>` seems pretty appealing to me.

...

To be honest, I much more worried about the blanket ban on copy.


This makes it unsuitable for standalone use and, even as member - the moment  you put it in you class, you will have to write quite non-standard copy constructor.


I even think, wouldn't it be possible to have some sort of attribute instead?

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


All else just works, no wrappers, no conversions, no nothing.
Just adjusting the fundamental rule that const on pointer does not affect const of the pointed-to.


joseph....@gmail.com

unread,
Oct 21, 2016, 6:51:43 AM10/21/16
to ISO C++ Standard - Future Proposals, jb...@me.com, mihailn...@gmail.com
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.

mihailn...@gmail.com

unread,
Oct 21, 2016, 9:00:53 AM10/21/16
to ISO C++ Standard - Future Proposals, jb...@me.com, mihailn...@gmail.com, joseph....@gmail.com


On Friday, October 21, 2016 at 1:51:43 PM UTC+3, joseph....@gmail.com wrote:
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.
...


I think overloads on the assignment and/or construction will pick the correct one.

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&"

}
 


Sadly, all this is still not enough to implement the above, even if such a attribute existed.

There must be a further rule that a copy of AutoConstT* must result in a const T*.

This is because we cant create/detect instances of objects which are const on creation.

We can't detect

const S b(other);

VS.

b(other);

Even if 'other' can be detected as const or not,
The ctor is the same, and const is applied to the instance after creation.

So given

struct  S
{
  AutoConstT* p;
};

when copy constructed from a const S& must become

struct S
{
  const T* p;
};

When constructed from S& will still become a

struct  S
{
  AutoConstT* p;
};

So there are at least two requirements to this typedef -
 1) access though a const pointer to it will make it const.
 2) When a const pointer to this type is copied, the type becomes permanent const.

joseph....@gmail.com

unread,
Oct 21, 2016, 1:22:19 PM10/21/16
to ISO C++ Standard - Future Proposals, jb...@me.com
On Friday, 21 October 2016 05:08:54 UTC+8, Jonathan Coe wrote:
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.

It's worth noting that with this new implicit conversion, the implementation of get_underlying pretty much becomes a call to const_cast:

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;
}

mihailn...@gmail.com

unread,
Oct 22, 2016, 4:34:54 AM10/22/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com
BTW, what is your use case? Or you meant from purely correctness point of view?

joseph....@gmail.com

unread,
Oct 22, 2016, 6:02:43 AM10/22/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com
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;
};

That's why I chose shared_ptr as an example, because it has no "safe" workaround, and is therefore somewhat incompatible with propagate_const as it stands.

mihailn...@gmail.com

unread,
Oct 22, 2016, 9:08:50 AM10/22/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com


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 :(

joseph....@gmail.com

unread,
Oct 22, 2016, 11:09:54 PM10/22/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com
On Saturday, 22 October 2016 21:08:50 UTC+8, mihailn...@gmail.com wrote:


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.

I think this problem is somewhat orthogonal to the concerns of propagate_const. You have made me think about this though. It is annoying that it is necessary to duplicate code a lot of the time when writing const versions of things. I may start another thread to have this discussion.

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 :(

I am increasingly thinking that, without more fundamental changes to the C++ language, const-propagation works best when kept separate from any indirection types. For example, if std::shared_ptr had built-in const-propagation, you would need a special function to allow you to copy it:

class container {
public:
   shared_ptr<int const> get_value() const { return value.copy(); }
   shared_ptr<int> get_value() { return value.copy(); }

private:
   shared_ptr<int> value;
};

In addition, calls to this API would be required to use this verbose copying syntax even though they aren't benefiting from the built-in const-propagation:

shared_ptr<int> v = c.get_value();
shared_ptr<int> w = v.copy(); // why the restriction; v cannot possibly become const!

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.

Jonathan Coe

unread,
Oct 23, 2016, 6:00:30 AM10/23/16
to std-pr...@isocpp.org, Sean Parent
`propagate_const` can handle composition well if copies are not required (shallow copies and const propagation do not work well together). 

For composition where copying is required, we are working on `std::indirect` which does const-propagation and deep copies.
I'll post a link once the pre-Issaquah mailing goes out.

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-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

mihailn...@gmail.com

unread,
Oct 23, 2016, 6:12:00 AM10/23/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com


On Sunday, October 23, 2016 at 6:09:54 AM UTC+3, joseph....@gmail.com wrote:
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.

Even if we restrict it to members only, the facts it forbids copy, kills "natural copying syntax" - one will have to write a copy ctor and assignment op, fiddling with get_underlying and what not.

struct S
{
 propagate_const
<shared_ptr<int>> p;
};

VS

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;
};


And this is just for a single propagator! I reckon this is hard to teach and reserved to non-junior programmers.

And of course, assignment const on non-const is possible.
Basically propagate_const will work correctly only if there is a deep copy or probably copy on write.

joseph....@gmail.com

unread,
Oct 23, 2016, 7:24:37 AM10/23/16
to ISO C++ Standard - Future Proposals, spa...@adobe.com, jb...@me.com

Look forward to it. I can't readily imagine what such a function/class would do.
 
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. 

If it's possible, you can be sure that someone, somewhere will want to do it. And they might even have good reason to do so :)

--
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.

joseph....@gmail.com

unread,
Oct 23, 2016, 9:59:57 AM10/23/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com

The fact that you need to call get_underlying shows that you are breaking the restrictions imposed by propagate_const. If propagate_const were implicitly convertible to T, you could implement the non-const copy constructor and assignment operator with no problem. But as stated, they wouldn't work too well with the existing C++ language and library features.

  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.

mihailn...@gmail.com

unread,
Oct 23, 2016, 11:59:58 AM10/23/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com


On Sunday, October 23, 2016 at 4:59:57 PM UTC+3, joseph....@gmail.com wrote:
...

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.

I meant, if the copy ctor and assign do a  deep copy propagate_const is all you need, no downsides - new data + new propagate_const instance.

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;
};

In this scenario propagate_const will behave completely correct.

In the shared_ptr scenario, copy-on-write is the only way to make the "casting-const" copying safe, Qt style.

So, no-copy, or deep-copy are the only two ways to use propagate_const in composition in another object.
One might argue, these are the only ways to do any const propagation in general, at least without some magic language support. 


Tony V E

unread,
Oct 23, 2016, 12:10:06 PM10/23/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com
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. 


Sent from my BlackBerry portable Babbage Device
Sent: Sunday, October 23, 2016 12:00 PM
To: ISO C++ Standard - Future Proposals
Subject: Re: [std-proposals] Re: [propagate_const] Implicit conversion to T

--
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.

joseph....@gmail.com

unread,
Oct 23, 2016, 1:52:19 PM10/23/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com
On 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).

I would be suspect of situations that use propagate_const ‎but don't have ownership semantics.
 
I'd say propagate_const implies value. const implies that you can't change an object's value (even if it doesn't guarantee it in practice). Lack of propagate_const is like the mutable keyword: it is an indication that some part of an object's representation does not affect its value, and can therefore be changed even if the object is const. propagate_const requires deep-copy or no copy simply because C++ copy operations take references to const. There is no logical reason why non-const objects using propagate_const shouldn't be copied; it just isn't supported well by C++. Ownership is a separate matter (though unique ownership by itself requires deep-copy or no copy).
 

Tony V E

unread,
Oct 23, 2016, 1:57:13 PM10/23/16
to ISO C++ Standard - Future Proposals, jb...@me.com, joseph....@gmail.com, mihailn...@gmail.com
Yes, value semantics is more accurate.

Sent from my BlackBerry portable Babbage Device
Sent: Sunday, October 23, 2016 1:52 PM

Jonathan

unread,
Oct 24, 2016, 2:40:59 AM10/24/16
to std-pr...@isocpp.org


On 23 Oct 2016, at 18:57, Tony V E <tvan...@gmail.com> wrote:

Yes, value semantics is more accurate.

Sent from my BlackBerry portable Babbage Device
Sent: Sunday, October 23, 2016 1:52 PM
To: ISO C++ Standard - Future Proposals
Subject: Re: [std-proposals] Re: [propagate_const] Implicit conversion to T

On 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).


As promised, here is 'indirect' which gives const-propagation and deep-copy.



mihailn...@gmail.com

unread,
Oct 24, 2016, 5:10:41 AM10/24/16
to ISO C++ Standard - Future Proposals


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.  


Jonathan Coe

unread,
Oct 24, 2016, 5:34:08 AM10/24/16
to std-pr...@isocpp.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.

Some other comments:

 - I believe the move-ctor and move-assignment (line 193 and 254) cannot be noexcept - they create a new control block.


Fixed in the impl.
 
And the fact that move-ops are not non-throwing is a considerable drawback.


move-construction and assignment of `indirect<T>` from another `indirect<T>` can be noexcept. Only when the template type is different do we need to allocate. 
 
 - 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.


LEWG requested a non-pointer-based name (initially it was cloned_ptr) as it's not a pointer. I'm sure we'll discuss naming further in Issaquah.
 
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.  



Agreed, this is a potentially useful tool, not a silver bullet. `propagate_const` is cheaper (free) when copying is not required.
 

--
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.

mihailn...@gmail.com

unread,
Oct 24, 2016, 8:57:04 AM10/24/16
to ISO C++ Standard - Future Proposals, jb...@me.com


On Monday, October 24, 2016 at 12:34:08 PM UTC+3, Jonathan Coe wrote:
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.
...
 
Are there plans to make use of small object optimization?

It seems delegating_control_block is always a fixed size one unique_ptr (~16 bytes)
and pointer_control_block is the same + copier + deleter (which will probably be zero size most of the time).

These two are heap allocated only for the type erasure to work.

Having SOO will make this class much, much, much more attractive to use!

I should not pay extra for std::indirect<char>( (char*)malloc(...), memcpy, free) to do the right thing, else I'll still have to write my own version like I do today.


P.S Another suggestion for a name - remote. It is a remote value - a value, not stored as part of the instance variable.
Also, the instance variable acts as a "remote control" on the value.

joseph....@gmail.com

unread,
Oct 26, 2016, 1:54:41 PM10/26/16
to ISO C++ Standard - Future Proposals, jb...@me.com
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.

I like the idea of a polymorphic value-like type. However, I believe that the proposed design is stuck in limbo between a pointer-like and value-like type:
  • An indirect is constructed from a pointer that it takes ownership of. Ideally, a value-like type would hide all memory management under the hood. Sure, make_indirect exists, but the smart pointer-esque, ownership-transferring constructor is still visible.
  • An indirect can be empty. Emptiness/nullness is a very pointer-like trait. Being able to convert a value-like type to bool to test for emptiness is very odd. A value-like type should always have a value.
  • Moving an indirect doesn't move the underlying object. Copying invokes the copy constructor, so why doesn't moving invoke the move constructor? I realize you gain noexcept status if you just move the internal pointer, but this isn't very value-like behaviour.
  • Deleters and copiers smell of resource management.

I suggest the following:

  • indirect should not use deleters or other special resource management functors.
  • The default constructor of indirect<T> should default construct a T (or not exist if T is not default constructible)
  • The copy and move constructors and assignment operators of indirect should copy and move the underlying objects (T must be copyable and movable)
  • An indirect<T> should be copy and move constructible and assignable from T.
  • The in_place technique can be used to construct indirect efficiently from other arguments (no additional copies; no adopted pointer).
  • indirect should not be convertible to bool. In fact, with this design it can no longer be in an empty state, so there is no reason for such a conversion to exist.
  • Since indirect implicitly upcasts, static_indirect_cast and dynamic_indirect_cast should be available for downcasting. However, const and reinterpret casts make no sense for a value-like type.

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.

Ville Voutilainen

unread,
Oct 26, 2016, 2:12:10 PM10/26/16
to ISO C++ Standard - Future Proposals, jbcoe
You're describing a completely different type with completely
different use cases.
For some (many) of the use cases the proposed indirect is catering
for, your proposed
type is completely useless.

joseph....@gmail.com

unread,
Oct 26, 2016, 2:36:37 PM10/26/16
to ISO C++ Standard - Future Proposals, jb...@me.com

Like what? My design seems perfectly in-line with the aims of the proposal, namely to have a wrapper type which can hold free-store-allocated objects polymorphically with value-like semantics.

Ville Voutilainen

unread,
Oct 26, 2016, 2:44:59 PM10/26/16
to ISO C++ Standard - Future Proposals
On 26 October 2016 at 21:36, <joseph....@gmail.com> wrote:
>> You're describing a completely different type with completely
>> different use cases.
>> For some (many) of the use cases the proposed indirect is catering
>> for, your proposed
>> type is completely useless.
>
>
> Like what? My design seems perfectly in-line with the aims of the proposal,

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.

joseph....@gmail.com

unread,
Oct 26, 2016, 3:11:01 PM10/26/16
to ISO C++ Standard - Future Proposals

Neither of these use cases is stated in the proposal. 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.

Ville Voutilainen

unread,
Oct 26, 2016, 3:13:28 PM10/26/16
to ISO C++ Standard - Future Proposals
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.

Well, it's apparently following LEWG's guidance.

Jonathan Coe

unread,
Oct 26, 2016, 3:17:20 PM10/26/16
to std-pr...@isocpp.org
Which bits are 'pointer-like'?
It's more value-like than it was but may have further to go.

> Well, it's apparently following LEWG's guidance.
>
> --
> 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.
> To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAFk2RUZRDT%2BD6W4%3DLkNAYSikXNP0opOtFcq9gPSymN8RzGMX-w%40mail.gmail.com.

joseph....@gmail.com

unread,
Oct 26, 2016, 3:50:15 PM10/26/16
to ISO C++ Standard - Future Proposals
On Thursday, 27 October 2016 03:17:20 UTC+8, Jonathan Coe wrote:


> 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.

I thought I outlined the parts that I consider pointer-like, but I can give examples comparing the current implementation and my example implementation. I'll use polymorphic as the name for my version, just to disambiguate:

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)


mihailn...@gmail.com

unread,
Oct 26, 2016, 4:28:17 PM10/26/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com
...


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 polymorphic 
The 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.

Bengt Gustafsson

unread,
Oct 26, 2016, 6:40:51 PM10/26/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com, mihailn...@gmail.com
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?

joseph....@gmail.com

unread,
Oct 27, 2016, 12:52:36 AM10/27/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com, mihailn...@gmail.com
On Thursday, 27 October 2016 04:28:17 UTC+8, mihailn...@gmail.com wrote:
...

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.  
 
I have no problem with a cloned_ptr proposal (essentially unique_ptr + deep copy (+ const-propagation, maybe). I just think the aims of the current proposal are confused. If it wants to be a polymorphic value-like type, I suggest something similar to what I presented. If it wants to be a deep-copying pointer-like type, then the chosen name should suggest that (I like deep_copy_ptr), and it should probably support the appropriate pointer-like operations (e.g. p = nullptr, p.get(), p.reset(x)).

Also, note that indirect<>, is intended to be constructed with make_indirect NOT with the ctor with a pointer.
 
This doesn't fix the fact that the only way to directly construct indirect, a supposedly value-like type, is by passing it ownership of a raw pointer.

About polymorphic 
The 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.
 
That's the idea. It behaves just like a stack-allocated object, but with polymorphism. If you can't afford the copies, then maybe you need a deep_copy_ptr instead.

Assignment from raw value however might be useful, especially move assignment.
 
Assignment from T is only really possible when you ditch the deleter and copier (it is only safe when using default_delete and default_copy).

Lastly, I find custom copier and deleter to be essential for the class to be useful.

Copier and deleter do not make sense for a value-like type. The only way you might be able to crowbar them in is if you disallow assignment from T, and also have a "creator" and "mover". However, I still think if you are going to support generic resource-management, a pointer-like type is what you need.

joseph....@gmail.com

unread,
Oct 27, 2016, 12:59:39 AM10/27/16
to ISO C++ Standard - Future Proposals, joseph....@gmail.com, mihailn...@gmail.com
On Thursday, 27 October 2016 06:40:51 UTC+8, Bengt Gustafsson wrote:
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?

You would need a creator and mover alongside the copier and deleter. However, as I just mentioned in my other reply, I think this design is ill-fitting for a value-like type, and would be better addressed with a pointer-like type.
 
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?

I don't think make_indirect supports use of custom copiers and deleters, much like make_unique doesn't support custom deleters, because it implicitly uses default construction (operator new) so default_delete and default_copy are appropriate. If you want to customize, use the constructor directly.

Nevin Liber

unread,
Oct 27, 2016, 1:10:27 AM10/27/16
to std-pr...@isocpp.org
On Wed, Oct 26, 2016 at 11:52 PM, <joseph....@gmail.com> wrote:
Copier and deleter do not make sense for a value-like type.

So standard containers aren't value-like types?  They contain allocators, which direct copying (at least the allocation part, as it is still an open question on whether or not copying should go through Allocator::construct) and deletion.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

joseph....@gmail.com

unread,
Oct 27, 2016, 2:04:14 AM10/27/16
to ISO C++ Standard - Future Proposals

I believe the deleters of unique_ptr and shared_ptr are primarily designed to handle custom generic resource management. Container allocators on the other hand are, AFAIK, designed primarily for custom memory management. However, if someone wanted to take an approach similar to STL allocators, I'm sure it would be possible.
Reply all
Reply to author
Forward
0 new messages