Extending lifetimebound attribute to return types

(Hi all, FYI this is my first post here. Apologies in advance if this isn’t the right forum.)

I’m interested in ideas for how/whether I can add a clang attribute that extends lifetimebound to apply to reference parameters on any function that returns a particular type, in the same way that a nodiscard attribute on type T automatically applies to any function that returns T.

My motivation is detailed below, but here is an example to show what I mean:

// A type that's tagged with the new attribute. The name of the attribute
// doesn't matter; feel free to suggest better names.
struct __attribute__((params_lifetimebound)) MyReturnType{};

// A function that returns MyReturnType. Because MyReturnType is tagged with
// params_lifetimebound, it should be as if each of the reference parameters
// (including the implicit this pointer for member functions) is tagged with
// lifetimebound.
MyReturnType Foo(int, const int&, int&&);

// Therefore these calls should result in a warning.
int some_int;
Foo(0, 0, std::move(some_int));  // Warns about second argument
Foo(0, some_int, 0);             // Warns about third argument

Ideally we could somehow make “reference-like” types like std::string_view count as references for the purposes of this warning, but I suppose that’s orthogonal (since it should work with lifetimebound as well).

Can anyone give me an idea of the process for adding such a thing? I guess the first step is: who do I need to agree this is a good idea?


I’m working on a C++ coroutine library, centered around a future-like return type called Task (a thin wrapper around a promise type). As part of this, I’ve established a “calling convention” saying that callers to a function that returns Task<T> must ensure that all reference arguments remain valid until the task it returns becomes ready. That is until the coroutine is logically finished (has hit co_return), not just until it hits the first suspension point and physically returns.

I think this is a useful convention because it is analogous to the usual rules for normal functions (can’t destroy an object being passed as an argument to a running function on another thread), and is good for performance (don’t need to make pessimistic copies everywhere). It’s also very easy to get right in the usual case of immediately co_awaiting the result of a coroutine:

// It's fine to use a temporary here even if CallBackend accepts the request
// by reference because the temporary won't be destroyed until the co_await
// expression evaluates, and it doesn't evaluate until CallBackend is finished.
const Response response = co_await CallBackend(MakeRequest());

That covers most uses in my experience. But in the particular case of a small function that just calls through to another, it’s easy to screw up and forget to co_await:

Task<Response> MakeRequestAndCallBackend() {
  // Oops, reference is destroyed as soon as the coroutine suspends.
  return CallBackend(MakeRequest());

If I could attach an attribute like params_lifetimebound to Task, I believe it would reliably prevent such bugs. I can write a clang-tidy check for this, but it should be caught as early as possible because it’s never safe in the ecosystem centered around my library.