So @Xazax-hun expressed my other concern. You want to effectively disable the [[clang::lifetimebound]] annotation for the case where the return type is not a reference. But of course the return type could itself contain pointers or references, and a full solution still needs verify the safety of those contained pointer/references, so it still needs annotations to indicate where those contained references came from.
The issue is that from a lifetime analysis point of view, both cases (where the return value is and is not a reference) need annotation, but those annotations may look significantly different because, lifetime-wise, you’re actually doing significantly different things.
I don’t recall how it works in the linked “Lifetime annotations for C++” RFC, but with the lifetime annotation system I work on, you can associate a “lifetime label alias” to the set of (contained/owned) lifetimes associated with the type of a specified template parameter.
Here’s a lifetime annotated example of a pseudo Optional template implemented with overloads of value_or() for when the rhs parameter is an rvalue reference and an lvalue reference. Notice that for the lvalue overload (that returns a reference), on line 24 the rhs parameter is annotated with LTP(a) which just associates the parameter (which is of reference type) with the lifetime label a. (Note that a can be used to refer to the enitire heirarchy of lifetimes associated with the reference parameter.)
But for the rvalue overload that always returns a non-reference, on line 17 the rhs parameter is annotated with LTP(_[a$]) which associates the “lifetime set alias” a$ to the set of lifetimes owned by the target object of the reference parameter. The lifetime of the reference target itself is excluded, as it will play no role in the return value.
So on line 24 in the lvalue overload, we are always returning a raw reference which always has only one target object (but any number of sub-target objects in a heirarchy). But on line 17 where the return type is non-raw-reference type T, the return value may be of some type which has multple target objects, and thus multiple “root” lifetimes, and those lifetimes are not associated directly with the reference parameter, but rather the target object of the reference parameter. So lifetime-wise, it’s more complicated to express what we’re doing on line 17 than the relatively simple thing we’re doing on line 24.
Currently this lifetime annotation system does not support conditional lifetime annotations, but even if it did, I can’t imagine it would be any prettier or clearer than having separate implementations for reference and non-reference return types. And as far as I know, Rust does not support conditional lifetime annotations either. And note that currently [[clang::lifetimebound]] is supported and just interpreted as a simple lifetime label.
edit:
I’ll just add that while this comment may demonstrate why it might be better to have separate overloads when using “full” lifetime annotations for this example, I wouldn’t consider it a conclusive argument against the original proposition.
Also, while our safety standards remain low enough that we can’t accept false positives, we also can’t have implicit default lifetime annotations like Rust does. So the places where lifetime analysis is desired will have to be explicitly lifetime annotated (so that any false positives would be the programmer’s fault). So that might argue in favor of whatever ergonomic features necessary to reduce the burden, which may include adding optional conditionality to the lifetime annotations (“legacy” and otherwise).
But if ever our standards rise to require full lifetime safety, and the acceptance of some false positives in return, then we can introduce default implicit lifetime annotations. And in C++ I feel it might be appropriate for those defaults to be significantly more aggressive than, for example, Rust’s. And as we see even with Rust’s conservative default implicit annotations, the prevalence of lifetime annotations is significantly reduced.
edit2:
Oops, I was incorrectly assuming the return type of value_or() would always be a reference if the element type was a reference type. Corrections attempted.