Title
Supporting copy-elision in function wrappers
Status
new
Section
[func.require]
Submitter
Tomasz Kamiński

Created on 2025-08-19.00:00:00 last changed 1 week ago

Messages

Date: 2025-08-23.09:20:47

Proposed resolution:

This wording is relative to N5014.

  1. Modify [func.require] as indicated:

    -3- Every call wrapper ([func.def]) meets the Cpp17MoveConstructible and Cpp17Destructible requirements. An argument forwarding call wrapper is a call wrapper that can be called with an arbitrary argument list and delivers the arguments to the target object as references. This forwarding step delivers rvalue arguments as rvalue references and lvalue arguments as lvalue references.:

    1. (3.?) — lvalue arguments as lvalues,

    2. (3.?) — xvalue arguments as xvalues,

    3. (3.?) — prvalue arguments as either prvalues or xvalues.

Date: 2025-08-23.12:10:43

The wording for argument forwarding call wrappers in [func.require] p3,

This forwarding step delivers rvalue arguments as rvalue references and lvalue arguments as lvalue references.

requires that each wrapper binds a temporary to rvalue reference (materializing it), and then pass that xvalue. This essentially codifies an implementation where wrappers provide an `operator()` that accepts Args&&.... This is fine for most of the wrappers.

For some wrappers more efficient implementation strategies are possible:

  • `bind_front(f)/bind_back(f)` without bound args could return a copy of `f`

  • bind_front<f>()/bind_back<f>() could produce a __function_wrapper<f>, that for function pointers can be invoked using a surrogate function call.

However, such implementation strategies are currently disallowed per [func.require] p3, as invoking the function wrapper with a prvalue `bind_front(f)(T())` requires a temporary to be materialized, and then moved into the parameter of `f`. For example:

struct M 
{
  M() { std::cout << "Default" < std::endl; }
  M(M&& m) { std::cout << "Move" < std::endl; }
};

struct F
{ 
  void operator()(M m) {} 
} f;

The call `f(M{})` will print only "`Default`" but `bind_front(f)(M{})` is required to produce "`Default`" and "`Move`". We should allow implementations to elide the move operations, but not require it.

The suggested changes by this issue have been implemented in libstdc++ for `bind_front/bind_back`.

History
Date User Action Args
2025-08-23 09:20:47adminsetmessages: + msg14945
2025-08-19 00:00:00admincreate