Add .emplace() method to std::unique_ptr and std::shared_ptr like it is in std::optional.

1,329 views
Skip to first unread message

anta...@gmail.com

unread,
Oct 30, 2017, 12:01:25 PM10/30/17
to ISO C++ Standard - Future Proposals
I've found really helpful in my own smart pointer classes to have something like .create(Args &&..args) method that creates an object for an owning shared pointer without the necessity for repeating the type in the make_* non-member function.

It's a shame that standard smart pointers don't have such method, so in order to create a value you always need to repeat the type name, while it is contained in the pointer type itself.

After that I realized, that std::optional<> has such method named .emplace() together with a free function make_optional<>. In some way std::optional<> is a lot like "in-place std::unique_ptr<>" with deep-copying by default.

So I thought that proposing a member function "ptr.emplace(Args&&... args)" for both std::unique_ptr and std::shared_ptr will be a really good idea, it will also fit well with std::optional. The naive (but working) implementation would be just:

void(?) unique_ptr<T>::emplace(Args &&... args) { *this = make_unique<T>(std::forward<Args>(args)...); }
void(?) shared_ptr<T>::emplace(Args &&... args) { *this = make_shared<T>(std::forward<Args>(args)...); }

What do you think? Are there reasons not to add such methods? Or was this discussed / proposed already?

Vasilii Babich.

anta...@gmail.com

unread,
Oct 30, 2017, 12:05:15 PM10/30/17
to ISO C++ Standard - Future Proposals, anta...@gmail.com
Perhaps being a nice substitution for "= make_...<T>(...)" the return type should be the reference to smart pointer and the return value should be *this.

Ville Voutilainen

unread,
Oct 30, 2017, 12:06:48 PM10/30/17
to ISO C++ Standard - Future Proposals, anta...@gmail.com
On 30 October 2017 at 18:05, <anta...@gmail.com> wrote:
> Perhaps being a nice substitution for "= make_...<T>(...)" the return type
> should be the reference to smart pointer and the return value should be
> *this.

The idea looks reasonable enough to me that I would recommend writing
a proposal for it.

>
> On Monday, October 30, 2017 at 8:01:25 PM UTC+4, anta...@gmail.com wrote:
>>
>> I've found really helpful in my own smart pointer classes to have
>> something like .create(Args &&..args) method that creates an object for an
>> owning shared pointer without the necessity for repeating the type in the
>> make_* non-member function.
>>
>> It's a shame that standard smart pointers don't have such method, so in
>> order to create a value you always need to repeat the type name, while it is
>> contained in the pointer type itself.
>>
>> After that I realized, that std::optional<> has such method named
>> .emplace() together with a free function make_optional<>. In some way
>> std::optional<> is a lot like "in-place std::unique_ptr<>" with deep-copying
>> by default.
>>
>> So I thought that proposing a member function "ptr.emplace(Args&&...
>> args)" for both std::unique_ptr and std::shared_ptr will be a really good
>> idea, it will also fit well with std::optional. The naive (but working)
>> implementation would be just:
>>
>> void(?) unique_ptr<T>::emplace(Args &&... args) { *this =
>> make_unique<T>(std::forward<Args>(args)...); }
>> void(?) shared_ptr<T>::emplace(Args &&... args) { *this =
>> make_shared<T>(std::forward<Args>(args)...); }
>>
>> What do you think? Are there reasons not to add such methods? Or was this
>> discussed / proposed already?
>>
>> Vasilii Babich.
>
> --
> 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/db8aed18-d3b3-4df6-8e15-15f8c50e4bed%40isocpp.org.

Nevin Liber

unread,
Oct 30, 2017, 12:38:37 PM10/30/17
to std-pr...@isocpp.org
On Mon, Oct 30, 2017 at 11:05 AM, <anta...@gmail.com> wrote:
void(?) unique_ptr<T>::emplace(Args &&... args) { *this = make_unique<T>(std::forward<Args>(args)...); }
void(?) shared_ptr<T>::emplace(Args &&... args) { *this = make_shared<T>(std::forward<Args>(args)...); }

Thoughts:
  • Neither shared_ptr nor unique_ptr store an allocator, so shared_ptr::emplace and unique_ptr::emplace are being tied to a particular allocation scheme.  This makes it harder when people refactor code to use other allocation schemes.
  • unique_ptr has a deleter, which may not go along with emplace calling "new".  Will this function still exist if default_delete<T> isn't being used?
  • Unless you templatize it, emplace will not work for polymorphic types, which IMO is one of the major use cases for unique_ptr (and a less major one for shared_ptr).
Note:  I'm not saying these are reasons I would vote against such a proposal; rather, these are things which need to be discussed in the proposal.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

anta...@gmail.com

unread,
Oct 30, 2017, 2:32:41 PM10/30/17
to ISO C++ Standard - Future Proposals
The first approach looks like this (attached).

1. Should I post it as a new topic or continue here?

2. Should I upload it to my hosting (or some preferred one) and post a link or attaching the file is fine?

Thanks for your time,
Vasilii Babich.
smart_pointer_emplace_1.txt

Jonathan Müller

unread,
Oct 30, 2017, 2:36:59 PM10/30/17
to std-pr...@isocpp.org
On 30.10.2017 17:37, Nevin Liber wrote:
> On Mon, Oct 30, 2017 at 11:05 AM, <anta...@gmail.com
> <mailto:anta...@gmail.com>> wrote:
>
> void(?) unique_ptr<T>::emplace(Args &&... args) { *this =
> make_unique<T>(std::forward<Args>(args)...); }
> void(?) shared_ptr<T>::emplace(Args &&... args) { *this =
> make_shared<T>(std::forward<Args>(args)...); }
>
>
> Thoughts:
>
> * Neither shared_ptr nor unique_ptr store an allocator, so
> shared_ptr::emplace and unique_ptr::emplace are being tied to a
> particular allocation scheme.  This makes it harder when people
> refactor code to use other allocation schemes.
> * unique_ptr has a deleter, which may not go along with emplace
> calling "new".  Will this function still exist if default_delete<T>
> isn't being used?
> * Unless you templatize it, emplace will not work for polymorphic
> types, which IMO is one of the major use cases for unique_ptr (and a
> less major one for shared_ptr).
>
> Note:  I'm not saying these are reasons I would vote against such a
> proposal; rather, these are things which need to be discussed in the
> proposal.

Furthermore:

* The smart pointers and optional behave very differently: optional has
value semantics, so it makes sense to say "create a new object".
I don't think it makes sense to ask the same from unique_ptr.

* If we have `emplace()` what about a constructor of the same form?
Yes, there's make_XXX but with class template argument deduction we
don't really want make_XXX anymore.

* If `ptr.emplace(obj)` works, what about `ptr = obj`? If you say no to
assignment because it doesn't make sense, why allow emplace?

* Why go through all that just to save a `= make_unique<T>(blah)`?
That's not too much typing.

anta...@gmail.com

unread,
Oct 30, 2017, 3:01:03 PM10/30/17
to ISO C++ Standard - Future Proposals
> The smart pointers and optional behave very differently: optional has 
value semantics, so it makes sense to say "create a new object". 
I don't think it makes sense to ask the same from unique_ptr. 

In my experience unique_ptr is quite similar to optional.
It is like an optional that doesn't hold the memory while there is no value in it and that works nicely with forward declarations.

> If we have `emplace()` what about a constructor of the same form? 
Yes, there's make_XXX but with class template argument deduction we 
don't really want make_XXX anymore.

As I understand the smart pointer make_* methods won't go anywhere after the template argument deduction comes in to play because they're not a helper functions for existing constructors of smart pointers. They can replace "make_unique<T>(args)" with "unique_ptr(new T(args))" with returning naked new to the common pattern (which is not looking good) and they can't replace make_shared at all because of optimized storage used in make_shared compared to "shared_ptr(new T(args))".

If we have emplace() in smart pointers perhaps we also would like to have in_place constructors for smart pointers similar to optional constructors with std::in_place. Those constructors would improve the usage of member initialization of smart pointer fields while emplace improves the usage of assigning of smart pointer variables. This will allow us to use "A::A() : p(in_place, args)" instead of "A::A() : p(make_unique<T>(args))" and "p.emplace(args)" instead of "p = make_unique<T>(args)".

> If `ptr.emplace(obj)` works, what about `ptr = obj`? If you say no to 
assignment because it doesn't make sense, why allow emplace?

object.emplace(value) doesn't always say "assign this value to that object" even with exactly one argument. For example std::set<T> has .emplace(value), but it doesn't allow assigning this value to it. emplace() methods in all standard classes say something like "create a new object from all those arguments and put it inside" and it works perfectly for both shared_ptr and unique_ptr.

> Why go through all that just to save a `= make_unique<T>(blah)`? 
That's not too much typing. 

The same can be said about "make_unique", but it was added, because it is a common pattern and it simplifies the code. "p = unique_ptr<T>(new T(args))" is more verbose than "p = make_unique<T>(args)" which is still unnecessary verbose compared to "p.emplace(args)". The amount of typing depends on the type T. :) If we introduce "auto" to reduce unnecessary typing in favour of better non-abbreviated type names (even if they're longer) in places where we have to specify them, I don't see why we won't remove those unneeded type name typings in other common code patterns.

Jonathan Müller

unread,
Oct 30, 2017, 3:07:48 PM10/30/17
to std-pr...@isocpp.org
On 30.10.2017 20:01, anta...@gmail.com wrote:
> > The smart pointers and optional behave very differently: optional has
> value semantics, so it makes sense to say "create a new object".
> I don't think it makes sense to ask the same from unique_ptr.
>
> In my experience unique_ptr is quite similar to optional.
> It is like an optional that doesn't hold the memory while there is no
> value in it and that works nicely with forward declarations.

But they're not. optional has a copy constructor, deep comparison etc.
It just has a similar syntax because (for some reason) it was modelled
after a pointer.

>
> > If we have `emplace()` what about a constructor of the same form?
> Yes, there's make_XXX but with class template argument deduction we
> don't really want make_XXX anymore.
>
> As I understand the smart pointer make_* methods won't go anywhere after
> the template argument deduction comes in to play because they're not a
> helper functions for existing constructors of smart pointers. They can
> replace "make_unique<T>(args)" with "unique_ptr(new T(args))" with
> returning naked new to the common pattern (which is not looking good)
> and they can't replace make_shared at all because of optimized storage
> used in make_shared compared to "shared_ptr(new T(args))".
>
> If we have emplace() in smart pointers perhaps we also would like to
> have in_place constructors for smart pointers similar to optional
> constructors with std::in_place. Those constructors would improve the
> usage of member initialization of smart pointer fields while emplace
> improves the usage of assigning of smart pointer variables. This will
> allow us to use "A::A() : p(in_place, args)" instead of "A::A() :
> p(make_unique<T>(args))" and "p.emplace(args)" instead of "p =
> make_unique<T>(args)".
>

This in place constructor is exactly what I meant there.
My comment is meant to be read as "will you also propose an in-place
constructor?"

> > If `ptr.emplace(obj)` works, what about `ptr = obj`? If you say no to
> assignment because it doesn't make sense, why allow emplace?
>
> object.emplace(value) doesn't always say "assign this value to that
> object" even with exactly one argument. For example std::set<T> has
> .emplace(value), but it doesn't allow assigning this value to it.
> emplace() methods in all standard classes say something like "create a
> new object from all those arguments and put it inside" and it works
> perfectly for both shared_ptr and unique_ptr.

It doesn't mean that but should you also add a corresponding `operator=`?

---

I think you are trying to turn smart pointers into indirect<T>, which is
already proposed:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0201r1.pdf

anta...@gmail.com

unread,
Oct 30, 2017, 4:17:42 PM10/30/17
to ISO C++ Standard - Future Proposals
> But they're not. optional has a copy constructor, deep comparison etc. 
It just has a similar syntax because (for some reason) it was modelled 
after a pointer. 

Even if we're speaking only about similarities in syntax, they're still similarities. Not only operator*(), operator->() and explicit operator bool(), but also reset() member function and make_*<T>(constructor_args) free function. And I see the emplace() method and in place constructors as good candidates for syntax similarities as well.

> This in place constructor is exactly what I meant there. 
My comment is meant to be read as "will you also propose an in-place 
constructor?" 

Yes, I think in-place constructor could be a good addition to such proposal.

> It doesn't mean that but should you also add a corresponding `operator=`? 

No, in my opinion assignment is not related to the emplace() function and I do not think it should be added to smart pointers. A case with single-argument emplace() call is just one case of a usual emplace() usage that takes arbitrary number and types of constructor arguments. Assignment a value to a smart pointer will just confuse everyone without adding anything, because "*p = value" syntax doesn't have any problems and is very similar to "p = value" syntax.


> I think you are trying to turn smart pointers into indirect<T>

I don't think so, indirect<T> solves its own goal of a deep-copyable indirect (possibly polymorphic) value type (being already renamed to polymorphic_value in the reference implementation).

All those wrapper classes (optional, shared_ptr, unique_ptr, indirect) serve different goals, but they all can contain a value of the type specified in the template parameter, they all share some of the public interface (like reset() call, like smart pointer semantics, like make_* helpers) - and I think they all should have similar interface to construct a new such value in the most efficient way, in both cases - when constructing a new wrapper object and when changing an existing wrapper object. First can be achieved by in_place constructors, second can be achieved by emplace() call.

For shared_ptr make_shared encapsulates the most-used case (not custom allocator), hiding the explicit "new" and "delete" calls. The "emplace" method and in_place constructor achieve the same but with more clear syntax (no repeating of the type name) and possibly more efficient (no move constructor / assigning).

For unique_ptr make_unique encapsulates the most-used case (not custom deleter), hiding the explicit "new" and "delete" calls. The same arguments go for "emplace" and in_place constructor here as for shared_ptr. If you use custom deleter, then it is likely you don't need the make function anyway, because the object is likely to be constructed somewhere else, not by a plain "new" call.

Arthur O'Dwyer

unread,
Oct 30, 2017, 7:03:43 PM10/30/17
to ISO C++ Standard - Future Proposals, anta...@gmail.com
On Monday, October 30, 2017 at 9:01:25 AM UTC-7, anta...@gmail.com wrote:
I've found really helpful in my own smart pointer classes to have something like .create(Args &&..args) method that creates an object for an owning shared pointer without the necessity for repeating the type in the make_* non-member function.

It's a shame that standard smart pointers don't have such method, so in order to create a value you always need to repeat the type name, while it is contained in the pointer type itself.

Technically, you're not "repeating" the type, since you name it only once.
Remember that C++ supports classical object-oriented programming; I would even go so far as to claim that heap-allocating single objects is a pattern primarily used by classical OO programmers. So we are talking about

    std::unique_ptr<Widget> mPtr;  // ...
    this->mPtr = std::make_unique<WidgetImpl>(some, args);

And you're proposing to rewrite that as something like

    std::unique_ptr<Widget> mPtr;  // ...
    this->mPtr.emplace<WidgetImpl>(some, args);

I don't see that as a valuable use of your time.
 
void(?) unique_ptr<T>::emplace(Args &&... args) { *this = make_unique<T>(std::forward<Args>(args)...); }
void(?) shared_ptr<T>::emplace(Args &&... args) { *this = make_shared<T>(std::forward<Args>(args)...); }

What do you think? Are there reasons not to add such methods? Or was this discussed / proposed already?

You should avoid adding a hard dependency between std::unique_ptr<T,D> and std::shared_ptr<T> on the one hand, and the global new/delete heap used by make_unique and make_shared on the other. Right now it's possible to use unique_ptr and shared_ptr with custom deleters for purposes other than classical OO. Creating a member function interface that is useful only for your specific use-case strikes me as going somewhat against the "C++ spirit" of small classes that provide a complete and orthogonal set of accessors.


Also, I personally find it easier to reason about value semantics than object semantics. Rather than think of "unique_ptr<T> p" as an object in memory that I "mutate" via member functions, I try to think of it more like an abstract label for a particular pointer value that I've created. So for me,

    p = make_unique<T>(some, args);
    // do some stuff
    p = nullptr;

perfectly captures my mental model of how pointers behave. I specifically avoid writing "object-semantic" code such as

    p.reset(new T(some, args));  // or p.emplace(some, args);
    // do some stuff
    p.reset();

even though it gives exactly the same codegen; I just find the value-semantic version easier to reason about.

HTH,
Arthur

Thomas Köppe

unread,
Oct 31, 2017, 10:33:54 AM10/31/17
to ISO C++ Standard - Future Proposals, anta...@gmail.com
It seems to me that emplacement should be provided by the deleter, since the deleter does the matching deleting. So perhaps a more fruitful pursuit would be to add an "emplace" to the deleter, and then have the unique_ptr's delete forward to that.

Tony V E

unread,
Oct 31, 2017, 11:03:59 AM10/31/17
to 'Thomas Köppe' via ISO C++ Standard - Future Proposals, anta...@gmail.com
And I was wondering if emplace was going to reuse the existing memory (when available) and that might also require coordination with the deleter.

Also, unique_ptr supports ‎fancy pointers. Keep that in mind.

Sent from my BlackBerry portable Babbage Device
From: 'Thomas Köppe' via ISO C++ Standard - Future Proposals
Sent: Tuesday, October 31, 2017 10:33 AM
To: ISO C++ Standard - Future Proposals
Subject: [std-proposals] Re: Add .emplace() method to std::unique_ptr and std::shared_ptr like it is in std::optional.

It seems to me that emplacement should be provided by the deleter, since the deleter does the matching deleting. So perhaps a more fruitful pursuit would be to add an "emplace" to the deleter, and then have the unique_ptr's delete forward to that.

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

Victor Dyachenko

unread,
Nov 1, 2017, 7:30:14 AM11/1/17
to ISO C++ Standard - Future Proposals, anta...@gmail.com
Reply all
Reply to author
Forward
0 new messages