Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::construct_at vs placement new; additional move/copy constructor call

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

like image 878
Daniel Langr Avatar asked Feb 11 '26 22:02

Daniel Langr


1 Answers

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)

like image 56
Artyer Avatar answered Feb 15 '26 19:02

Artyer