In C++17 and above, guaranteed copy elision means that it's possible to return non-moveable objects frun a chain of functions, all the way to the ultimate caller:
struct NonMoveable {
NonMoveable() = default;
NonMoveable(NonMoveable&&) = delete;
};
NonMoveable Foo() { return NonMoveable(); }
NonMoveable Bar() { return Foo(); }
NonMoveable Baz() { return Bar(); }
NonMoveable non_moveable = Baz();
Is there some trick to disable guaranteed copy elision for a particular type, so that it's not possible to write functions like Bar
and Baz
above that pass through a NonMoveable
object obtained from another function? (I'm agnostic as to whether Foo
should be disallowed or not.)
I know this is a weird request. If you're interested in what I'm trying to do: I'm working on a coroutines library, where the convention is that if you return a Task
from a function then all of the reference parameters for that function need to remain valid until the task is ready, i.e. until a co_await
expression for the task evaluates. This all totally works out fine if a coroutine is called from another coroutine, since I've arranged for the Task
type to be non-moveable and accepted by value: you can't do anything with it except immediately co_await
it, and any temporaries you provide in the call to the child coroutine will live until the co_await
expression evaluates.
Except you can do one more thing with Task
: write functions like Bar
and Baz
above that do return Foo()
instead of co_return co_await Foo()
. If there is an argument to Foo
involved it might be a temporary, in which case it's safe to co_return co_await Foo(...)
but not return Foo(...)
.
This can get surprisingly subtle. For example a std::function<Task(SomeObj)>
internally contains a return
statement and will happily bind to a lambda that accepts const SomeObj&
. When it's called it will provide the lambda a reference to its by-value parameter, which is destroyed when the lambda suspends.
I'm looking for a more elegant way to prevent this problem, but in the meantime I'd like to make the problematic form just not compile (which also helps identify the set of problems in user code). I expect this is not possible, but perhaps there is some trick I haven't thought of.
std::move
it. But that disables the copy elision for movable objects too. You can use a concept / enable_if. – Murrelet