I have a function that produces-returns a value of a class type Value.
Value compute();
Now, I want to create a new object of type Value in an existing storage, and initialize it with the value returned by compute. When I use placement new for this initialization, and deferred temporary materialization/NRVO is applied, then, the new object is initialized “directly”.
new (storage) Value(compute()); // single constructor of Value called
However, when I use std::construct_at, then, an additional call of Value's copy/move constuctor is called:
std::construct_at<Value>(storage, compute()); // additional copy/move constructor called
This is not only less efficient in general, but also unusable for non-movable types. My question is whether there is a way to use std::construct_at in this scenario without the additional copy/move constructor call. (I don't think so, but want to be sure.)
More elaborated online demo: https://godbolt.org/z/sc98Px3hW
The reason this happens is that compute() is used to materialise a temporary to bind to a reference parameter of construct_at. construct_at basically takes auto&&..., so there will be a Value&& parameter, which will be used to construct at the given pointer. C++ does not currently have a mechanism to bind prvalues directly to function arguments.
You can usually use something with a converting constructor:
template<class F>
struct delayed_call {
F f;
constexpr operator decltype(std::declval<F&&>()())() && {
return std::move(*this).f();
}
};
std::construct_at(p, delayed_call{ compute });
// Or something more complicated with a lambda:
std::construct_at(p, delayed_call{ [&] -> Value {
int x = f();
return Value{ .member1 = x, .member2 = 2*x };
} });
This does not work for 'sufficiently bad' types (anything with an unconstrained constructor similar to T::T(auto) would be ambiguous or call the converting constructor instead of the conversion operator)
Otherwise, the placement new expression can be used in place of std::construct_at in most situations. The only exception is that it is not constexpr in C++20 and C++23.
Also, this copy elision is RVO/URVO ('unnamed return value optimisation', prvalue initialising at storage without an intermediary temporary), NRVO is something different (local function variable is an alias for what the returned prvalue initialises)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With