Re: [std-proposals] Copy eliding emplacement functions for containers.

175 views
Skip to first unread message

Richard Smith

unread,
Sep 20, 2017, 4:34:13 AM9/20/17
to std-pr...@isocpp.org
On 19 September 2017 at 09:37, <len...@gmail.com> wrote:
Hi,

Guaranteed copy elision in C++17 is pretty great. This way I can simply return by value in my factory functions. Basically these factory functions act like external constructors (but not perfectly).

#include <memory>
extern widget factory();

int main() {
   
auto w1 = widget{ factory() };
   
auto w2 = new widget{ factory() };
   
auto w3 = std::unique_ptr<widget>{new widget{ factory() }};

   
return 0;
}

Basically all three cases have guaranteed copy elision. It would be nice if we could put elements from these kind of factory functions into a container and eliding copying at the same time. Now the only interface for something like this is the emplace* family of functions, but they only work with constructors. I suggest a function with the following signature.

//for vector
template <class Func, class... Args>
reference emplace_back_from_function
(Func func, Args... args);

The container then calls placement new inside somewhere:
new(pos) value_type{ func(std::forward<Args>(args)...) };
Maybe allocators need an updated interface for it too as placement new through the indirection of allocator_traits::construct brakes guaranteed copy elision (I think).

This way we could make collections of non-copyable and even non-movable types as guaranteed copy elision is allowed for these too.

std::make_shared could have a std::make_shared_from_function counterpart too. Maybe there are other parts of the standard library that calls constructors of user specified types that could make advantage of this.

Thoughts?

The above design prevents guaranteed copy elision for arguments of 'func'. Instead, there's an even simpler pattern:

template<typename Func>
reference emplace_using(Func func);

... that simply does "new(pos) value_type(func());" to construct the value. However, adding one of those per emplace function seems very heavyweight; it would seem nicer to add a wrapper class template that could be passed to ordinary emplace functions, eg:

vec.emplace_back( emplace_using( [&]{ return factory_function(args); } ) );

where emplace_with is a type that wraps a function and implicitly calls it when needed to convert to the return type of the function:

template<typename Func> struct emplace_using {
  Func func;
  emplace_using(Func func) : func(std::move(func)) {}
  operator decltype(auto)() { return func(); }
};

This almost works... the trouble is that perfect forwarding gets in the way (unsurprisingly, since "perfect" forwarding is imperfect for prvalues). allocator::construct will do

  new (loc) T(forward<Arg>(arg)...)

where we need the argument to the constructor to be a *prvalue* of type T. Possibly we could modify the construction logic to use a mechanism that acts like forward<T> for most Ts, but invokes the wrapped function when T is emplace_using<Fn>.

Lénárd Szolnoki

--
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/0144b887-c694-4f3e-b368-32645a1562d9%40isocpp.org.

len...@gmail.com

unread,
Sep 20, 2017, 6:23:45 AM9/20/17
to ISO C++ Standard - Future Proposals
This is really great. I was working on something similar but then I bumped into a compiler problem. Clang++ doesn't seem to do guaranteed copy elision when implicit conversion is involved. The following fails to compile on clang++

#include <memory>


template <typename Func>
struct emplace_using {
    Func func;
    emplace_using(Func func) : func(std::move(func)) {}
    operator decltype(auto)() { return func(); }
};

struct A {
    A(const A&) = delete;
    A(A&&) = delete;
};

A Afactory() { return A{}; }

int main() {
    auto ptr =
        std::make_unique<A>(emplace_using{[]() { return Afactory(); }});
}

It complains about calling the deleted move constructor. However on g++ it compiles it without warnings. A similar problem occurs when the move constructor is explicitly delegated from an initializer list. Clang++ supposedly supports guaranteed copy elision and indeed it works for simple cases.
 
This almost works... the trouble is that perfect forwarding gets in the way (unsurprisingly, since "perfect" forwarding is imperfect for prvalues). allocator::construct will do

  new (loc) T(forward<Arg>(arg)...)

where we need the argument to the constructor to be a *prvalue* of type T. Possibly we could modify the construction logic to use a mechanism that acts like forward<T> for most Ts, but invokes the wrapped function when T is emplace_using<Fn>.

I don't fully comprehend the problem described here, but I'm not too experienced with value categories and the quirks of not so perfect forwarding. Maybe some examples could help.

Ray Hamel

unread,
Sep 20, 2017, 12:29:37 PM9/20/17
to ISO C++ Standard - Future Proposals
Wouldn't it be far simpler just to add a method relevant_stl_collections<T>::copy_back(T value)?

Ville Voutilainen

unread,
Sep 20, 2017, 12:32:56 PM9/20/17
to ISO C++ Standard - Future Proposals
On 20 September 2017 at 19:29, Ray Hamel <rayg...@gmail.com> wrote:
> Wouldn't it be far simpler just to add a method
> relevant_stl_collections<T>::copy_back(T value)?


What is this copy_back function supposed to do when T is neither
copyable nor movable?

Ray Hamel

unread,
Sep 20, 2017, 12:34:30 PM9/20/17
to ISO C++ Standard - Future Proposals
Anyway, plain push_back has a T&& overload, which isn't eligible for copy elision but does move-construct the element from the argument, and move construction is about as fast as copy elision.

Ville Voutilainen

unread,
Sep 20, 2017, 12:37:22 PM9/20/17
to ISO C++ Standard - Future Proposals
This is less about what is fast as it it about what is possible.

Ray Hamel

unread,
Sep 20, 2017, 1:00:11 PM9/20/17
to ISO C++ Standard - Future Proposals
It constructs it in-place if it's an rvalue (copy/move elision) or fails to compile if it's an lvalue, which is the desirable behavior.

Ville Voutilainen

unread,
Sep 20, 2017, 1:05:17 PM9/20/17
to ISO C++ Standard - Future Proposals
Please show me a version where swizzle actually stores an element into
a container.
Reply all
Reply to author
Forward
0 new messages