9

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

6
  • One of the differences - std::construct_at<T>(storage) always value initializes an object ~ new (storage) T{}. Commented 23 hours ago
  • 1
    So the topic is that std::construct_at() blocks NRVO? That might lead to a more precise title ("Can I use NRVO with std::construct_at() ?" or similar). Commented 22 hours ago
  • @BenVoigt No, it does not block NRVO. Without NRVO, there would be one additional move/copy constructor call (in both cases): godbolt.org/z/qxnfT7a6M. Commented 22 hours ago
  • Use uninitialized_move? godbolt.org/z/W9caod7E6 Commented 21 hours ago
  • In almost all programs I'd just use Value v = compute(); and let the compiler + optimizer sort it out. Only if this was happening in a provable hot path and had an actual/provable impact would I care to look at it. But I think such a situation would be rare, so mostly I'd just leave it alone. Commented 15 hours ago

1 Answer 1

8

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)

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.