| # Lifetime Annotations for C++ |
| |
| [TOC] |
| |
| Summary: We propose a scheme for annotating lifetimes for references and |
| pointers in C++. |
| |
| Note: This is a living document that is intended to always reflect the most |
| current semantics and syntax of the lifetime annotations. |
| |
| ## Introduction {#introduction} |
| |
| This document proposes an attribute-based annotation scheme for C++ that |
| describes object lifetime contracts. Lifetime annotations serve the following |
| goals: |
| |
| * They allow relatively cheap, scalable, local static analysis to find many |
| common cases of heap-use-after-free and stack-use-after-return bugs. |
| * They allow other static analysis algorithms to be less conservative in their |
| modeling of the C++ object graph and potential mutations done to it. |
| * They serve as documentation of an API’s lifetime contract, which is often |
| not described in the prose documentation of the API. |
| * They enable better C++/Rust and C++/Swift interoperability. |
| |
| The annotation scheme is inspired by Rust lifetimes, but it is adapted to C++ so |
| that it can be incrementally rolled out to existing C++ codebases. Furthermore, |
| the annotations can be automatically added to an existing codebase by a tool |
| that [infers the annotations](lifetimes_static_analysis.md) based on the current |
| behavior of each function’s implementation. |
| |
| While the annotation scheme can express a large subset of Rust’s lifetime |
| semantics, we have omitted some constructs that we do not expect to be necessary |
| for our purposes. For example, |
| [lifetime bounds](https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds) |
| (e.g. `'a: 'b` or `T: 'a`) may be needed rarely enough that we can do without |
| them, and |
| [higher-ranked trait bounds](https://doc.rust-lang.org/reference/trait-bounds.html#higher-ranked-trait-bounds) |
| (e.g. `where for<'a> F: Fn(&'a i32)`) are possible only for function types, |
| which is what they are usually needed for. |
| |
| We are aware of two existing schemes for annotating lifetimes and flagging |
| lifetime violations in C++; we describe them in the sections |
| “[Alternative considered: `[[clang::lifetimebound]]`](#alternative-considered-clang-lifetimebound)” |
| and |
| “[Alternative considered: P1179 / `-Wdangling-gsl`](#alternative-considered-p1179-wdangling-gsl)” |
| below. Both of these schemes have limitations that make them unsuitable for our |
| purposes. We plan to enable our lifetime analysis to understand the existing |
| annotations by translating them into our annotation syntax internally (where |
| possible). |
| |
| ## Proposal {#proposal} |
| |
| ### Examples {#examples} |
| |
| To give a feel for how the annotations work in practice, we will first show some |
| examples. |
| |
| Here is a simple example: |
| |
| ```c++ |
| const std::string& [[lifetime(a)]] smaller( |
| const std::string& [[lifetime(a)]] s1, |
| const std::string& [[lifetime(a)]] s2) { |
| if (s1 < s2) { |
| return s1; |
| } else { |
| return s2; |
| } |
| } |
| ``` |
| |
| The annotation states that both s1 and `s2` may be referred to by the return |
| value of the function. This implies that the lifetime of the return value is the |
| shorter of the lifetimes of `s1` and `s2`. In Rust, this example would be |
| expressed as follows: |
| |
| ```rust |
| pub fn smaller<'a>(s1: &'a String, s2: &'a String) -> &'a String; |
| ``` |
| |
| Note how the syntax is broadly similar. The main difference is that, unlike in |
| Rust, our proposal does not require lifetimes to be declared. |
| |
| A lifetime annotation placed after a member function refers to the lifetime of |
| the object the member function is called on: |
| |
| ```c++ |
| struct string { |
| // The returned pointer should not outlive ``*this``. |
| const char *[[lifetime(a)]] data() const [[lifetime(a)]]; |
| }; |
| ``` |
| |
| Similar to Rust, `[[lifetime(static)]]` is used to denote a static lifetime. A |
| common pattern is for a class to have a static function returning a reference to |
| some default value: |
| |
| ```c++ |
| class Options final { |
| public: |
| // ... |
| static const Options &[[lifetime(static)]] DefaultOptions(); |
| // ... |
| }; |
| ``` |
| |
| The attribute can be applied to references that appear inside a more complex |
| type expression. For example: |
| |
| ```c++ |
| const std::vector<const A *[[lifetime(static)]]> &[[lifetime(static)]] |
| get_static_as(); |
| ``` |
| |
| This expresses that both the reference to the vector and the pointers to the |
| `A`s contained inside it have static lifetimes. |
| |
| This roughly corresponds to the following in Rust (with the difference that, |
| unlike C++ pointers, Rust references cannot be null): |
| |
| ```rust |
| fn get_static_as() -> &'static CxxVector<&'static A>; |
| ``` |
| |
| ### Lifetimes {#lifetimes} |
| |
| Lifetimes are associated with certain types that we call *reference-like types*. |
| A reference-like type is one of the following: |
| |
| * A pointer (except pointers to functions and pointers to members) |
| * A reference (except references to functions) |
| * A user-defined type that has been annotated as having lifetime parameters. |
| (We will explain user-defined reference-like types in detail in a later |
| section.) |
| |
| The reason that pointers to functions and references to functions do not have |
| lifetimes is to be consistent with Rust, where `fn` types do not have lifetimes |
| either. In C++, the function that a pointer or reference refers to almost always |
| exists for the duration of the program execution. There are some exceptions, |
| such as functions created by a JIT compiler or functions in plugins loaded and |
| unloaded at runtime. Such functions may be destroyed before the program exits, |
| but we consider them to be unusual enough that we don't support annotating their |
| lifetimes. |
| |
| Pointers to members don't have lifetimes because they aren't pointers in the |
| narrower sense. A pointer to member doesn't refer to a specific object in |
| memory; rather, it can be used to refer to a specific member of any object of a |
| given type. In implementation terms, a pointer to member is not an address but |
| an offset. |
| |
| Lifetimes are annotated using the new attribute `lifetime`[^1]. The attribute |
| takes one or several lifetime names as arguments. |
| [Appendix A](#appendix-a-lifetime-attribute-specification) contains a formal |
| description of the attribute syntax. |
| |
| For brevity, lifetimes may be implicitly inferred in some situations; this is |
| referred to as *lifetime elision*, and we describe the specific rules for this |
| later. |
| |
| There are two lifetime names with special meaning: |
| |
| * `static`: A lifetime that lasts for the duration of the program. |
| * `unsafe`: A lifetime that cannot otherwise be represented correctly using |
| lifetime annotations. We will discuss the semantics of an unsafe lifetime in |
| more detail below. |
| |
| In addition, there are two types of lifetimes that cannot be named in a lifetime |
| attribute but that are implicitly associated with reference-like types in |
| certain situations: |
| |
| * *Local lifetime*: The lifetime of a pointer to a variable with automatic |
| storage duration. |
| * *Unknown lifetime*: A lifetime that has not been annotated and cannot be |
| implicitly inferred. \ |
| The concept of unknown lifetimes is important because it allows us to |
| migrate a codebase to lifetime annotations incrementally. Tools that verify |
| lifetime correctness should assume that operations involving unknown |
| lifetimes are lifetime-correct; this avoids generating large numbers of |
| nuisance errors for code that has not been annotated yet. Note that this |
| makes unknown lifetimes fundamentally different from unsafe lifetimes. |
| |
| We call static, unsafe, local, and unknown lifetimes *constant lifetimes*. We |
| call all other lifetimes *variable lifetimes*; this reflects the fact that they |
| may be substituted by other lifetimes. |
| |
| The `lifetime` attribute can be applied to reference-like types in function |
| signatures, variable declarations (including member variable declarations), |
| alias declarations, and to user-defined reference-like types when referring to |
| static members of such types. The sections below give details on how the |
| attribute can be applied to these constructs and what the semantics are in each |
| case. |
| |
| Note that, unlike in Rust, lifetimes are not part of the type. For the purposes |
| of C++ semantics (e.g. function overloading), two types that differ only in |
| their lifetime annotations are considered the same type. This is by design: We |
| don’t want to change the semantics of existing code by adding lifetimes, and |
| this is one of the reasons we have chosen to use C++ attributes; the C++ |
| standard allows compilers to ignore attributes they don’t know, which implies |
| that they have no effect on the C++ semantics. |
| |
| ### Lifetime-correctness {#lifetime-correctness} |
| |
| The implementation of a function must be lifetime-correct. This section explains |
| what that means. |
| |
| Most expressions propagate lifetimes in ways that are straightforward. We will |
| therefore explain lifetime-correctness rules only for those cases that are |
| non-trivial. |
| |
| Dereferencing a pointer or accessing the value referred to by a reference is |
| lifetime-correct in exactly the following cases: |
| |
| * If its lifetime is static or a variable lifetime |
| * If its lifetime is local and the access happens during the lifetime of the |
| corresponding local variable. |
| |
| Dereferencing a pointer with unknown lifetime or accessing the value referred to |
| by a reference with unknown lifetime is not lifetime-correct, but tools should |
| not emit lifetime verification errors in these cases. |
| |
| `operator new` returns a pointer with unsafe lifetime. `operator delete` takes a |
| pointer parameter that has unsafe lifetime. |
| |
| Initializing or assigning an object of reference-like type with another object |
| is always correct if the lifetimes of the two objects are the same. |
| |
| In addition, there are a number of cases where it is permissible to initialize |
| or assign an object of reference-like type with another object that has |
| different lifetimes. We call such an operation a lifetime conversion. |
| |
| To define lifetime correctness of conversions, we first need to define what it |
| means for one lifetime to *outlive* another: |
| |
| * Any lifetime outlives itself. |
| * The `static` lifetime outlives any variable or local lifetime. |
| * Any variable lifetime outlives any local lifetime. |
| * A local lifetime `local1` outlives another local lifetime `local2` if the |
| object associated with `local1` outlives the object associated with `local2` |
| according to C++’s lifetime rules. |
| * The unsafe lifetime does not outlive any lifetime except itself, and no |
| other lifetime outlives the unsafe lifetime. |
| * The unknown lifetime does not outlive any lifetime except itself, and no |
| other lifetime outlives the unknown lifetime. However, tools should not emit |
| lifetime verification errors for lifetime conversions involving unknown |
| lifetimes. |
| |
| Note that no variable lifetime `a` outlives any other variable lifetime `b`; our |
| annotation scheme does not permit specifying lifetime bounds between lifetimes |
| in the way that |
| [Rust does](https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds). |
| |
| Here are the rules for the correctness of lifetime conversions: |
| |
| * Lifetime-converting a non-const pointer of type `T_from |
| *[[lifetime(l_from)]]` to type `T_to *[[lifetime(l_to)]]` is |
| lifetime-correct if and only if |
| * `l_from` outlives `l_to`, and |
| * any lifetimes in `T_from` and `T_to` are identical. |
| * Lifetime-converting a const pointer of type `T_from * const |
| [[lifetime(l_from)]]` to type `T_to * const [[lifetime(l_to)]]` is |
| lifetime-correct if and only if |
| * `l_from` outlives `l_to`, and |
| * converting `T_from` to `T_to` is lifetime-correct. |
| * The rules for converting references are analogous to those for converting |
| pointers. |
| * An object of a class `T` with lifetime parameters may not be converted to an |
| object of the same class `T` but with different lifetime parameters; see |
| also the sections on [variance](#variance) and |
| [special member functions](#special-member-functions). |
| |
| We will describe the lifetime-correctness rules for certain other constructs in |
| the specific sections that deal with those constructs below. |
| |
| ### `lifetime_cast` {#lifetime_cast} |
| |
| To permit building safe abstractions on top of APIs that use unsafe lifetimes, |
| we provide a way to cast unsafe lifetimes to safe lifetimes and vice versa using |
| a function template called `lifetime_cast`[^2]. A `lifetime_cast` is similar to |
| C++ cast operations such as `const_cast` and `static_cast` but may only be used |
| to change lifetimes. |
| |
| Obviously, code that uses `lifetime_cast` must guarantee that the operation is |
| actually lifetime-correct, i.e. that there is no risk of a use-after-free. Like |
| unsafe code in Rust, uses of `lifetime_cast` should therefore be carefully |
| reviewed and constrained to small parts of the codebase. |
| |
| `lifetime_cast` is a function template defined suitably such that the call |
| `lifetime_cast<T>(e)` evaluates to `e` and does not perform any copy or move |
| operations. Tools will assume that the lifetimes of the result are those |
| specified in the template argument for `T`. Apart from lifetime attributes, `T` |
| must be the same as `decltype(e)`. |
| |
| A typical use case for `lifetime_cast` would be building a container such as |
| `std::vector` on top of raw memory allocation primitives such as `operator new`. |
| For example, one of the constructors for a vector might look like this: |
| |
| ```c++ |
| |
| template <class T> |
| void vector<T>::vector(size_t size) [[lifetime(a)]] |
| : size_(size), capacity_(size), data_(lifetime_cast<T *[[lifetime(a)]]>(new |
| T[size])) {} |
| ``` |
| |
| ### Concise syntax using macros {#concise-syntax-using-macros} |
| |
| Even with lifetime elision, there is a potential concern that the annotations |
| will introduce excessive clutter. A lifetime in Rust typically requires only two |
| characters, e.g. `'a`. In contrast, the attribute proposed above, |
| `[[lifetime(a)]]`, requires at least 15 characters, or more if the attribute is |
| scoped inside a namespace. |
| |
| To reduce verbosity, we suggest providing a macro with a short name that expands |
| to the actual lifetime attribute. The single-character macro name “`$`” is not |
| in widespread use in many codebases[^3]; a codebase maintainer would obviously |
| want to consider carefully what to use it for, but we think lifetimes could be a |
| worthwhile use. In addition to a general <code>$(<em>lifetime</em>)</code> |
| macro, we could also define lifetime macros <code>$a</code> through |
| <code>$z</code> to allow an even more concise annotation. As an example, this is |
| what the <code>smaller()</code> example from the beginning would look like with |
| this concise syntax: |
| |
| ```c++ |
| const std::string &$a smaller( |
| const std::string &$a s1, |
| const std::string &$a s2); |
| ``` |
| |
| For a more extensive example, see |
| [appendix B](#appendix-b-std-string_view-with-lifetime-annotations), which shows |
| what `std::string_view` would look like with these annotations. |
| |
| Every codebase can of course define its own macro shortcuts that work within the |
| context of the codebase. A more traditional and still concise macro name would |
| be `LT`, with additional macros `LT_A` through `LT_Z` for concise single-letter |
| lifetimes. |
| |
| For brevity, in the examples that follow, we will use the `$` convention. |
| |
| ### Pointers and References {#pointers-and-references} |
| |
| As already noted, pointers and references can be annotated with a lifetime, |
| which specifies the lifetime of the object the pointer or reference refers to |
| (the *pointee*). The lifetime of the pointee must outlive the lifetime of the |
| pointer or reference itself. |
| |
| For example, let’s look at the example of a double pointer `int * $a * $b`. The |
| annotation `$b` on the outer pointer specifies the lifetime of the inner pointer |
| of type `int *`; the annotation `$a` on the inner pointer specifies the lifetime |
| of the int. When these lifetime variables are substituted with constant |
| lifetimes, the lifetime substituted for `$a` must outlive the lifetime |
| substituted for `$b`. This ensures that the `int` lives for at least as long as |
| the `int *` pointer that refers to it. |
| |
| ### Functions {#functions} |
| |
| Lifetime attributes may be placed in the parameter types and return type of a |
| function or function type. In addition, for non-static member functions, a |
| lifetime attribute may be placed after the function declaration to describe the |
| lifetime of the object the member function is called on, i.e. the lifetime of |
| the implicit `this` parameter. |
| |
| If a translation unit contains multiple declarations of the same function |
| (including its definition), the lifetime attributes in all declarations must be |
| the same. |
| |
| As in Rust, a function is considered to be parameterized by the lifetimes that |
| appear in its signature. To express this, a `lifetime_param` attribute |
| containing the variable lifetime parameters may be placed in front of the |
| function definition, like this: |
| |
| ```c++ |
| [[lifetime_param(a)]] |
| int *$a ReturnPtr(int *$a p) { |
| return p; |
| } |
| ``` |
| |
| However, for brevity, this `lifetime_param` attribute may and should be left out |
| in most cases. The exception to this is when the signature of the function |
| contains a function type that itself contains lifetimes; in this case, a |
| `lifetime_param` attribute must be added to disambiguate whether the lifetime |
| should be considered a parameter of the function type or the function. For |
| example: |
| |
| ```c++ |
| // Lifetime $a is a parameter of the function type int*(int*). |
| void AddCallback(std::function<int *$a(int *$a) [[lifetime_param(a)]]> f); |
| |
| // Lifetime $a is a parameter of the function AddCallback(). |
| [[lifetime_param(a)]] |
| void AddCallback(std::function<int *$a(int *$a)> f, int *$a p); |
| ``` |
| |
| Lifetime parameters on function types are analogous to |
| [higher-ranked trait bounds](https://doc.rust-lang.org/reference/trait-bounds.html#higher-ranked-trait-bounds) |
| in Rust; unlike Rust, however, we only allow this concept in the context of |
| function types, which is where it is typically required. |
| |
| TODO: Show an example where we're passing a pointer to a local variable into the |
| callback and discuss how this is allowed in the HRTB case but not the other |
| case. |
| |
| Lifetime-converting a function pointer `from` to a function pointer `to` of the |
| same type but with different lifetimes is lifetime-correct if `from` has either |
| the same lifetimes as `to` or lifetimes that are more permissive. This means |
| that we must be able to substitute the lifetime parameters of `from` with |
| lifetime parameters of `to` such that: |
| |
| * Every parameter of `to` is lifetime-convertible to the corresponding |
| parameter of `from`. (Note the direction of the conversion, which is |
| reversed from what one might initially expect. The idea is that `from` needs |
| to be able to stand in for `to`, so we need to be able to convert the |
| parameters of `to` to the parameters of `from`.[^4]) |
| |
| * The return type of `from` is lifetime-convertible to `to`. |
| |
| Similarly, a virtual member function `Derived::f` that overrides a base class |
| function `Base::f` must have either the same lifetimes or lifetimes that are |
| more permissive. This means that there we must be able to substitute the |
| lifetime parameters of `Derived::f` with lifetime parameters of `Base::f` such |
| that: |
| |
| * Every parameter of `Base::f` is lifetime-convertible to the corresponding |
| parameter of `Derived::f`. |
| * The return type of `Derived::f` is lifetime-convertible to `Base::f`. |
| |
| A function call is lifetime-correct if the lifetime parameters of the callee can |
| be substituted by lifetimes from the caller in such a way that converting all |
| arguments to the respective parameter lifetimes is lifetime-correct. If no such |
| substitution can be found, the function call is not lifetime-correct. |
| |
| Here is an example that illustrates how this works: |
| |
| ```c++ |
| void copy_ptr(int *$x from, int *$x *$y to) { |
| *to = from; |
| } |
| |
| int *$a return_ptr(int *$a p) { |
| int* copy; |
| copy_ptr(p, ©); |
| return copy; |
| } |
| ``` |
| |
| First of all, the `copy` pointer is inferred to have lifetime `$a` because it is |
| used in the return statement. Let’s use the name `$local1` for the lifetime of |
| the `copy` variable itself. |
| |
| Now let’s look at the call to `copy_ptr`. If we make the substitutions `$x` = |
| `$a` and `$y` = `$local1`, we see that the lifetimes of the arguments are |
| identical to those of the parameters, so it is trivially correct to |
| lifetime-convert them. |
| |
| Assume now that `return_ptr` had been declared with different lifetimes for its |
| parameter and return type: |
| |
| ```c++ |
| int *$a return_ptr(int *$b p) { |
| int* copy; |
| copy_ptr(p, ©); // Error, not lifetime-correct. |
| return copy; |
| } |
| ``` |
| |
| Again, the `copy` pointer has lifetime `$a`. If we choose the substitution |
| `$x` = `$a`, we can lifetime-convert the second argument but not the first |
| argument (we need an i`nt *$a` but we have an `int *$b`). If we choose `$x` = |
| `$b`, we can lifetime-convert the first argument but not the second argument (we |
| need an `int *$b *` but we have an `int *$a *`). |
| |
| Because there is no substitution we can make for `$x` that allows a |
| lifetime-correct conversion of the arguments of `copy_ptr` to the respective |
| parameter lifetimes, the call is not lifetime-correct. |
| |
| ### Lifetime elision {#lifetime-elision} |
| |
| As in Rust, to avoid unnecessary annotation clutter, we allow lifetime |
| annotations to be elided (omitted) from a function signature when they conform |
| to certain regular patterns. Lifetime elision is merely a shorthand for these |
| regular lifetime patterns. Elided lifetimes are treated exactly as if they had |
| been spelled out explicitly; in particular, they are subject to lifetime |
| verification, so they are just as safe as explicitly annotated lifetimes. |
| |
| We adopt the same |
| [lifetime elision rules](https://doc.rust-lang.org/reference/lifetime-elision.html) |
| as Rust. We will expand on the rationale for this below, but first let us |
| present the rules. |
| |
| We call lifetimes on parameters *input lifetimes* and lifetimes on return values |
| *output lifetimes*. There are three rules: |
| |
| 1. Each input lifetime that is elided (i.e. not stated explicitly) becomes a |
| distinct lifetime. |
| 2. If there is exactly one input lifetime (whether stated explicitly or |
| elided), that lifetime is assigned to all elided output lifetimes. |
| 3. If there are multiple input lifetimes but one of them applies to the |
| implicit `this` parameter, that lifetime is assigned to all elided output |
| lifetimes. |
| |
| If a function signature contains a function type (in a parameter or the return |
| value), lifetime elision is performed separately for any lifetimes that occur in |
| this function type, independent of the lifetimes in the surrounding function |
| signature. Any elided lifetimes within the function type become lifetime |
| parameters of the function type. See also the discussion of lifetime parameters |
| on function types in [this section](#pointers-and-references). |
| |
| Lifetime elision rules have two requirements: |
| |
| 1. They need to be easy for a programmer to remember and apply. |
| 2. They should be applicable to as many functions as possible, i.e. they should |
| maximize the percentage of functions whose lifetime semantics correspond to |
| the elided lifetimes. Put differently, they should minimize the percentage |
| of functions which need explicit, non-elided lifetimes. |
| |
| There is some alignment between these requirements, but some tension too. |
| Working out what the best set of rules is likely requires quite a bit of |
| testing. Instead of doing this, we have for the time being adopted the same set |
| of rules that Rust uses, which presumably have a lot of collective experience |
| embedded in them. The underlying assumption is that Rust and C++ functions do |
| similar things with lifetimes in their interfaces; this assumption seems |
| passable, though surely not perfect. An added benefit of using the Rust rules is |
| that programmers using both languages don't need to keep two sets of rules in |
| their head. |
| |
| Once we have static analysis tooling that can run on real-world codebases, we |
| may do some tweaking of the lifetime elision rules, but there would need to be |
| clear benefits to justify giving up commonality with Rust. |
| |
| Introducing lifetimes to a codebase will have to happen incrementally, and this |
| requires some additional considerations. During the transition, there will be |
| some files that have not yet been annotated, and we may indeed decide to exclude |
| some parts of the code base from annotation permanently. Lifetime elision should |
| not be applied to files that have not been annotated or verified for lifetime |
| correctness; instead, the lifetimes should be assumed to be unknown, as |
| described [above](#lifetimes). |
| |
| We propose using a pragma or suitable comment string to mark source files where |
| lifetime elision is allowed, e.g.: |
| |
| ```c++ |
| #pragma lifetime_elision |
| ``` |
| |
| ### Static member variables and non-member variables {#static-member-variables-and-non-member-variables} |
| |
| Static member variable declarations and non-member variable declarations need |
| not contain lifetime attributes but may do so for clarity. |
| |
| In general, it may not even be possible to annotate a local variable correctly |
| with the current lifetime annotation syntax. This happens when a local variable |
| may refer to objects of different, unrelated lifetimes. Such a situation is |
| entirely permissible; lifetime inference and verification tools need to deal |
| with this by using a richer internal representation for the lifetimes of local |
| variables. |
| |
| If a variable has static storage duration, all lifetimes in its type are |
| implicitly assumed to be static. Any manual annotations that are present may |
| only specify the lifetimes `static` or `unsafe`. |
| |
| Taking the address of a static member variable or non-member variable yields a |
| pointer with a lifetime that depends on the variable’s storage duration. If the |
| variable has static storage duration, the pointer has `static` lifetime. If the |
| variable has automatic storage duration, the pointer has a local lifetime. |
| |
| ### Classes and non-static member variables {#classes-and-non-static-member-variables} |
| |
| A class may be annotated with one or several lifetime parameters by placing the |
| new attribute `lifetime_param` in the class declaration, and a class annotated |
| in this way is considered to be a reference-like type. All declarations of a |
| class must be annotated with the same lifetime parameters. (See |
| [appendix A](#appendix-a-lifetime-attribute-specification) for a formal |
| description of the attribute syntax.) |
| |
| When lifetime parameters are substituted with constant lifetime arguments, all |
| of these lifetime arguments must outlive the lifetime of the object they are |
| applied to. This is analogous to the corresponding rule for pointers and |
| references. |
| |
| Lifetime parameters are necessary when an object of the class contains |
| references to data that has a different lifetime than the object itself; the |
| standard C++ types |
| <code>[std::string_view](https://en.cppreference.com/w/cpp/string/basic_string_view)</code> |
| and <code>[std::span](https://en.cppreference.com/w/cpp/container/span)</code> |
| are examples of this. |
| |
| The lifetime parameters may be used in the declarations of non-static member |
| functions and non-static member variables of the class. |
| |
| As an example, here is how parts of `std::string_view` might be annotated[^5]: |
| |
| ```c++ |
| class [[lifetime_param(a)]] string_view { |
| string_view(const char *$a data, size_type len) |
| : ptr_(data), len_(len) {} |
| |
| |
| const char *$a data() const { return ptr_; } |
| |
| string_view $a substr(size_t pos, size_t count) const; |
| |
| private: |
| const char *$a ptr_; |
| size_t len_; |
| }; |
| ``` |
| |
| All reference-like types in the declaration of a non-static member variable must |
| be annotated with the lifetimes `static`, `unsafe`, or one of the lifetime |
| parameters of the class. |
| |
| If a class contains owning pointers to manually allocated memory, these pointers |
| will typically be annotated with an `unsafe` lifetime. Collection types such as |
| `std::vector` are examples of this. Member functions that provide access to the |
| owned memory will typically perform a `lifetime_cast` to the lifetime of the |
| owning object. For example, `std::vector::at()` has the lifetime signature `T& |
| $a std::vector<T>::at(size_type) $a`. |
| |
| A class is not required to use any of its lifetime parameters; it may declare |
| lifetime parameters solely for the purpose of associating a lifetime with |
| objects of the class. |
| |
| #### Derived classes {#derived-classes} |
| |
| Derived classes inherit the lifetime parameters of their base classes. It is not |
| permissible to add lifetime parameters to a derived class; in other words, all |
| lifetime parameters need to be declared on the base class. If a derived class |
| has multiple base classes, only one of these base classes may declare lifetime |
| parameters. |
| |
| TODO: Having a derived class “silently” inherit the lifetime parameters of its |
| base classes isn’t great because it doesn't make the lifetime parameters of the |
| derived class visible at the place where it is defined. We should instead |
| consider requiring the lifetime parameters to be re-declared. |
| |
| The motivation for this rule is to cover the case where a call to a virtual |
| member function in the base class may access member variables of reference-like |
| type in a derived class. A similar situation exists when casting a pointer from |
| the base class to the derived class. In both cases, we want all lifetimes that |
| are relevant to the derived class to be known on the base class. |
| |
| ### Special member functions {#special-member-functions} |
| |
| Special member functions can be annotated with lifetimes just like other member |
| functions, but they deserve special attention because they can be implicitly |
| declared and because they are central to the semantics of C++ value types. |
| |
| The default constructor and destructor are trivial as they only take a single |
| reference-like parameter, the implicit `this` parameter, so we will not discuss |
| them further. |
| |
| The lifetimes in the copy and move operations for a type `A` without lifetime |
| parameters are as follows (using `$s` and `$o` as mnemonics for “self” and |
| “other”): |
| |
| ```c++ |
| A(const A& $o) $s; |
| A(A&& $o) $s; |
| A& $s operator=(const A& $o) $s; |
| A& $s operator=(A&& $o) $s; |
| ``` |
| |
| Conveniently, these are the lifetimes that are implied by lifetime elision, so |
| they would be omitted in practice. |
| |
| The implication of these lifetimes is that it is possible to move or assign an |
| object of type `A` to another object with a different lifetime. |
| |
| The situation is slightly more complicated for a type with lifetime parameters. |
| As an example, consider the following class: |
| |
| ```c++ |
| struct [[lifetime_param(p)]] B { |
| int* $p p; |
| }; |
| ``` |
| |
| (The special member functions are implicitly defaulted.) |
| |
| The lifetimes of the special member functions on `B` are as follows: |
| |
| ```c++ |
| B(const B $p & $o) $s; |
| B(B $p && $o) $s; |
| B& $s operator=(const B $p & $o) $s; |
| B& $s operator=(B $p && $o) $s; |
| ``` |
| |
| Note that while the lifetimes of the “self” and “other” objects themselves are |
| different, their lifetime parameters are the same. This implies that the copy |
| and move operations cannot extend the lifetime of `B::p`. |
| |
| The lifetimes above are not the same as those implied by lifetime elision. |
| Classes with lifetime parameters that use the defaulted copy and move operations |
| need to add explicitly defaulted definitions for these operations. |
| |
| ### Alias declarations {#alias-declarations} |
| |
| Alias declarations can declare lifetime parameters in a similar way to classes. |
| These lifetime parameters can then be used on the right-hand side of the alias |
| declaration. In addition, any alias declaration, whether it has lifetime |
| parameters or not, can use the lifetimes `static` and `unsafe` on its right-hand |
| side. |
| |
| If an alias declaration is contained inside a class, its right-hand side may |
| *not* use any lifetime parameters of that class. This is because, in general, an |
| instance of the alias type has no connection to an instance of the class. |
| |
| Here is an example for an alias declaration with lifetime parameters, again |
| using `std::string_view`: |
| |
| ```c++ |
| class [[lifetime_param(a)]] string_view { |
| public: |
| // ... |
| using const_iterator [[lifetime_param(i)]] = const char *$i; |
| const_iterator $a begin() const; |
| const_iterator $a end() const; |
| // ... |
| }; |
| ``` |
| |
| Note that the `lifetime_param` attribute comes after the type alias name, |
| whereas in a class declaration it comes before the class name. This may seem |
| inconsistent, but the placement is dictated by the C++ grammar. |
| |
| So far, we have pretended that `string_view` is a class, but it is in fact |
| itself an alias declaration for `basic_string_view<char>`, and this alias |
| declaration therefore has a lifetime parameter: |
| |
| ```c++ |
| template <class T> class [[lifetime_param(a)]] basic_string_view { |
| // ... |
| }; |
| |
| using string_view [[lifetime_param(a)]] = basic_string_view<char> $a; |
| ``` |
| |
| The interpretation of this is that `string_view` is a type with a lifetime |
| parameter `a`, and that this lifetime parameter should be forwarded to the |
| lifetime parameter of `basic_string_view<char>`. |
| |
| ### Templates {#templates} |
| |
| A function template or class template may be annotated with `lifetime` |
| attributes and, in the case of class templates, a `lifetime_param` attribute, |
| just like a non-template function or class. |
| |
| Explicit template instantiations may not contain `lifetime` attributes. |
| |
| Lifetime-correctness of a template may, in general, depend on the template |
| arguments. A template is lifetime-correct if there exists at least one set of |
| arguments for which no specialization exists, that do not result in substitution |
| failure, and for which the specialized template is lifetime-correct. |
| |
| In general, therefore, lifetimes can only be inferred and verified on a template |
| instantiation. This implies that inference and verification may need to be done |
| multiple times if the same template instantiation is used in multiple |
| translation units. This is slightly unfortunate, but there does not seem to be a |
| good way around it, and it mirrors the fact that such a template instantiation |
| is also compiled multiple times. |
| |
| To the extent that it is possible to infer and verify lifetimes on the template |
| itself, independent of the template arguments, tooling should do this. In other |
| words, a lifetime-correctness error should be flagged if there is no set of |
| template arguments for which the specialized template is lifetime-correct. |
| Lifetimes should be inferred if they are correct for any template arguments for |
| which no specialization exists and which do not result in a substitution |
| failure. |
| |
| Partial template specializations should be treated the same way as primary |
| template definitions: Tooling should infer and verify lifetimes on the partial |
| specialization to the extent that this can be done independent of the template |
| arguments. |
| |
| Full template specializations should be treated the same way as non-template |
| functions and classes: Lifetimes should be inferred and verified on the full |
| template specialization. |
| |
| When analyzing code that uses a template for which partial or full |
| specializations exist, tooling must of course make sure to refer to the correct |
| specialization. |
| |
| #### Function templates {#function-templates} |
| |
| A function template’s type arguments as well as other dependent types may, in |
| general, be reference-like types. Therefore, when a function template |
| instantiation is used (either by calling it or by taking its address), tooling |
| should do the following: |
| |
| * Verify the lifetime-correctness of the function template instantiation. |
| * Infer lifetimes for all reference-like types in the signature of the |
| function template instantiation, except for reference-like types that occur |
| in the function template itself and are already annotated with lifetimes |
| there. |
| |
| The lifetimes inferred for the specialized function template should be used when |
| inferring and verifying lifetimes of functions that use the specialized function |
| template. |
| |
| #### Class templates {#class-templates} |
| |
| The type arguments to a class template may be reference-like types. A class |
| template that is specialized with reference-like types in this way is itself |
| considered to be a reference-like type. The specialized class template has a |
| lifetime parameter for each reference-like type that occurs in the template |
| arguments; these lifetime parameters are in addition to any lifetime parameters |
| that are annotated on the class template itself using the `lifetime_param` |
| attribute. The lifetime parameters associated with a template argument are |
| implicitly propagated to all uses of that argument in the class template. |
| |
| TODO: Add a discussion of template template arguments |
| |
| Lifetimes are assigned to a specialized class template’s lifetime arguments as |
| for any other reference-like type, i.e. depending on the context in which the |
| specialized class template is used they may be explicitly annotated, implied by |
| lifetime elision, or inferred. However, there is a syntactical difference: When |
| lifetimes are explicitly annotated, they are placed in the template arguments |
| instead of after the type, as they would be for other lifetime parameters. For |
| example, here a function that takes a vector of pointers and returns an element |
| of the vector: |
| |
| ```c++ |
| int* $a get_ith(const std::vector<int* $a>& $b v, size_t i) { |
| return v[i]; |
| } |
| ``` |
| |
| TODO: Discuss dependent types. |
| |
| A member function of a specialized class template need not be lifetime-correct |
| for all possible assignments of the lifetime parameters associated with the |
| template arguments. |
| |
| Instead, we only require that every ODR-use of a member function of a |
| specialized class template is lifetime-correct for the lifetimes assigned to the |
| lifetime parameters for that particular use. |
| |
| A (slightly contrived) example will help to illustrate why these rules are |
| written the way they are. |
| |
| ```c++ |
| template <class From, class To> |
| struct Convert { |
| To convert(From from) { return from; } |
| }; |
| |
| void constify(int* [[lifetime(a)]] p, |
| const int *[[lifetime(a)]] *[[lifetime(b)]] pp) { |
| Convert<int*, const int*> c; |
| *pp = c.convert(p); |
| } |
| ``` |
| |
| The specialized class template `Convert<int*, const int*>` has two lifetime |
| parameters: one lifetime parameter (which we will call `x`) for the `int*` |
| template argument, and one lifetime parameter (which we will call `y`) for the |
| `const int*` template argument. |
| |
| The `Convert::convert()` member function is not lifetime-correct if we consider |
| `x` and `y` to be arbitrary variable lifetimes, as it is not lifetime-correct to |
| lifetime-convert an `int *`with lifetime `x` to a `const int *` with lifetime |
| `y`. |
| |
| However, for the use of Convert in the declaration of `c`, we infer that both |
| `x` and `y` should be substituted by the lifetime `a`. `Convert::convert()` is |
| lifetime-correct when x and y are substituted in this way. |
| |
| TODO: Do we need to make this distinction between lifetime parameters and the |
| lifetimes they are substituted with, or can we make the substitution directly? |
| |
| ### Variance {#variance} |
| |
| [As in Rust](https://doc.rust-lang.org/nightly/nomicon/subtyping.html), we need |
| to establish some variance rules for type and lifetime parameters, but the |
| specific rules differ slightly from Rust. |
| |
| * Const references and pointers `const T &` and `const T *` are covariant with |
| respect to `T`. |
| * Non-const references and pointers `T &` and `T *` are invariant with respect |
| to `T`. |
| * Class templates are invariant with respect to their type parameters |
| (including lifetimes contained in them). |
| * All lifetime-parameterized types (classes and alias declarations) are |
| invariant with respect to their lifetime parameters. |
| |
| The last two rules differ from Rust, which |
| [infers](https://rustc-dev-guide.rust-lang.org/variance.html) the variance of |
| type and lifetime parameters on user-defined types. Unlike Rust generics, C++ |
| class templates are invariant with respect to their type parameters[^6], and we |
| want to be consistent with this. |
| |
| Regarding lifetime parameters on types, we restrict ourselves to invariance for |
| simplicity. Rust infers the variance of lifetime parameters from the way they |
| are used in the definition of the type, but in C++, this is impossible to do, at |
| least on a single-translation-unit basis, as a lifetime-parameterized class may |
| only be forward-declared in the current translation unit. For simplicity, and |
| consistency with template parameters, we have therefore decided that lifetime |
| parameters will always be invariant, as we expect this to be sufficient in |
| practice. If this turns out to be too limiting, we may need to provide a way of |
| annotating the variance of lifetime parameters. |
| |
| ## Alternative considered: `[[clang::lifetimebound]]` {#alternative-considered-clang-lifetimebound} |
| |
| Clang already provides a limited ability to annotate lifetimes with the |
| [`[[clang::lifetimebound]]` attribute](https://clang.llvm.org/docs/AttributeReference.html#lifetimebound)[^7]. |
| Quoting from the documentation: |
| |
| > The `lifetimebound` attribute on a function parameter or implicit object |
| > parameter indicates that objects that are referred to by that parameter may |
| > also be referred to by the return value of the annotated function (or, for a |
| > parameter of a constructor, by the value of the constructed object). |
| |
| If the lifetime annotation is applied to aggregates (arrays and simple structs), |
| those aggregates are considered to refer to any pointers or references |
| transitively contained within them. |
| |
| Here, again, is the `smaller()` example, but annotated with |
| `[[clang::lifetimebound]]`: |
| |
| ```c++ |
| const std::string& smaller( |
| const std::string& s1 [[clang::lifetimebound]], |
| const std::string& s2 [[clang::lifetimebound]]); |
| ``` |
| |
| The attribute may also be applied to a member function to indicate that the |
| lifetime of the return value corresponds to the lifetime of the object. Here is |
| an example from the `[[clang::lifetimebound]]` documentation: |
| |
| ```c++ |
| struct string { |
| // The returned pointer should not outlive ``*this``. |
| const char *data() const [[clang::lifetimebound]]; |
| }; |
| ``` |
| |
| This is an example of the very common case where a member function returns a |
| pointer or reference to part of the object, or to another object owned by it. |
| |
| The `[[clang::lifetimebound]]` attribute provides a way to express lifetimes in |
| many common scenarios, but it does have its limitations: |
| |
| * There is no way to differentiate between different lifetimes. |
| * There is no way to annotate a static lifetime. |
| * The attribute attaches to function parameters and always implicitly refers |
| to the outermost reference-like type[^8]; it is not possible to attach it to |
| part of a type (e.g. to the `T *` in a `const std::vector<T *> &`). |
| |
| * The single lifetime is implicitly applied to the outermost reference-like |
| type in the function’s return type (or the value of the constructed object, |
| in the case of a constructor). Again, it is not possible to associate the |
| lifetime with inner reference types in the return value (e.g. the `T *` in |
| `const std::vector<T *> &`). |
| |
| * The lifetime of a constructor parameter can be associated with the lifetime |
| of the object being constructed, i.e. with the lifetime of the `this` |
| pointer, but this isn’t possible in other member functions. In other words, |
| a member function cannot associate the lifetime of a parameter with the |
| lifetime of the object the member function is called on. |
| |
| * There is no way to add a lifetime parameter to a struct. |
| |
| ## Alternative considered: P1179 / `-Wdangling-gsl` {#alternative-considered-p1179-wdangling-gsl} |
| |
| The WG21 proposal |
| [P1179](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1179r1.pdf) |
| describes a static analysis that aims to prevent many common types of |
| use-after-free. It uses an attribute-based annotation scheme to describe the |
| lifetime contracts of functions and to annotate user-defined types containing |
| indirections. |
| |
| Preliminary implementations of this scheme exist in MSVC and a |
| [fork](https://github.com/mgehre/llvm-project) of Clang. In addition, Clang |
| trunk implements statement-local warnings inspired by the scheme, which are |
| enabled by the on-by-default flag `-Wdangling-gsl`. |
| |
| The scheme has both advantages and disadvantages compared to the scheme proposed |
| here: |
| |
| * **Advantages** |
| * Can express independent pre- and postconditions for lifetimes, e.g. to |
| annotate `std::swap(ptr1, ptr2)`, where the lifetimes of the pointers |
| after the call are swapped compared to before the call. |
| * Can diagnose some cases of iterator invalidation. |
| * **Disadvantages** |
| * User-defined types can only be annotated as having one of a class of |
| fairly specific lifetime semantics (“SharedOwner”, “Owner”, “Pointer”); |
| arbitrary annotation of classes with lifetime parameters is not |
| possible. |
| * Cannot refer to lifetimes of pointers in template arguments, e.g. no way |
| to express `int *$a return_first(const vector<int *$a> &$b v);` |
| * Annotations can be verbose and syntactically removed from the objects |
| they refer to. |
| |
| We believe the limitations of this scheme will restrict its usefulness in the |
| use cases we are interested in. A more in-depth comparison of P1179 with our |
| proposed scheme can be found |
| [here](https://discourse.llvm.org/t/rfc-lifetime-annotations-for-c/61377#lifetime-safety-preventing-common-dangling-wg21-proposal-p1179-wdangling-gsl-18). |
| |
| ## Appendix A: Lifetime attribute specification {#appendix-a-lifetime-attribute-specification} |
| |
| This appendix describes where lifetime attributes may appear and what arguments |
| they can take. |
| |
| ### Temporary syntax {#temporary-syntax} |
| |
| We are currently still experimenting with the exact syntax and semantics for the |
| lifetime annotations. While we are doing so, we will use the general-purpose |
| `annotate` and |
| <code>[annotate_type](https://discourse.llvm.org/t/rfc-new-attribute-annotate-type-iteration-2/61378)</code> |
| attributes as stand-ins for the new attributes proposed below. |
| |
| ### Attribute definitions {#attribute-definitions} |
| |
| We introduce two new attributes, `lifetime` and `lifetime_param`. In practice, |
| these would be scoped to a namespace (probably `clang`), but for ease of |
| exposition, we assume they are in the global namespace. |
| |
| #### Attribute `lifetime_param` {#attribute-lifetime_param} |
| |
| This attribute may be applied to the following: |
| |
| * **A class definition** (more formally, it may appear in the |
| attribute-specifier-seq of a |
| [class-head](https://eel.is/c++draft/class.pre#nt:class-head)) |
| * **An |
| [alias-declaration](http://eel.is/c++draft/dcl.pre#nt:alias-declaration)** |
| (specifically, the attribute-specifier-seq following the identifier) |
| |
| The attribute takes one or more arguments. Each of these arguments must be an |
| identifier[^9]; each argument defines a lifetime parameter for the corresponding |
| class. |
| |
| If the class definition or alias declaration is nested within a class that |
| itself has a `lifetime_param` attribute, none of the lifetime parameter names of |
| the outer class may be used as lifetime parameter names on the nested class |
| definition or alias declaration. |
| |
| #### Attribute `lifetime` {#attribute-lifetime} |
| |
| This attribute may be applied to the following: |
| |
| * **Types and pointer operators in a function declaration, member function |
| declaration, non-static member variable declaration, or alias declaration** |
| \ |
| More formally, within the return type, the |
| [trailing-return-type](https://eel.is/c++draft/dcl.dcl#nt:trailing-return-type) |
| or the |
| [parameter-declaration-clause](https://eel.is/c++draft/dcl.dcl#nt:parameter-declaration-clause) |
| of a function declaration or member function declaration, within a |
| non-static member variable declaration, or within the |
| [defining-type-id](http://eel.is/c++draft/dcl.name#nt:defining-type-id) of |
| an alias declaration the attribute may appear: |
| * In the attribute-specifier-seq of a |
| [decl-specifier-seq](https://eel.is/c++draft/dcl.spec.general#nt:decl-specifier-seq) |
| * In the attribute-specifier-seq of a |
| [type-specifier-seq](https://eel.is/c++draft/dcl.dcl#nt:type-specifier-seq) |
| * In the attribute-specifier-seq of a |
| [ptr-operator](https://eel.is/c++draft/dcl.dcl#nt:ptr-operator) (both |
| within a [declarator](https://eel.is/c++draft/dcl.dcl#nt:declarator) and |
| an |
| [abstract-declarator](https://eel.is/c++draft/dcl.dcl#nt:abstract-declarator)) |
| * **A non-static member function declaration** \ |
| More formally, within a |
| [member-declarator](https://eel.is/c++draft/class.mem.general#nt:member-declarator) |
| for a non-static member function, the attribute may appear in the |
| attribute-specifier-seq of the |
| [parameters-and-qualifiers](https://eel.is/c++draft/dcl.decl.general#nt:parameters-and-qualifiers). |
| |
| The attribute takes one or more arguments, each of which must be an identifier |
| or the keyword `static`. We call these identifiers *lifetime names*. |
| |
| In addition, the following constraints apply: |
| |
| * When the `lifetime` attribute is applied to a **type**, the type must be a |
| class type or alias declaration whose definition contains a `lifetime_param` |
| attribute. |
| |
| The `lifetime` attribute must have the same number of arguments as the |
| `lifetime_param` attribute on the corresponding class or alias declaration. |
| (These arguments define lifetime parameters for the object instance.) |
| |
| * When the lifetime attribute is applied to a **pointer operator**, it must |
| take exactly one argument. (This defines a lifetime for the object |
| referenced by the pointer operator.). |
| |
| * When the `lifetime` attribute is applied to a **non-static member function |
| declaration**, it must take exactly one argument. (This defines a lifetime |
| for the implicit object parameter). |
| |
| * Every lifetime name that appears in a **function’s return value** must |
| either be `static` or also appear either in |
| |
| * the function’s parameter list, or |
| * the `lifetime` attribute for the implicit object parameter (in the case |
| of a non-static member function), or |
| * the `lifetime_param` attribute of the class (in the case of a non-static |
| member function). |
| |
| * For every **constructor of a class that has a `lifetime_param` attribute**, |
| every lifetime name that appears in the `lifetime_param` attribute must |
| appear in the constructor’s parameter list. |
| |
| * Every lifetime name that appears in a **non-static member variable |
| declaration** must either be `static` or one of the lifetime parameters |
| declared in a `lifetime_param` attribute on the class containing the member |
| variable declaration. |
| |
| * Every lifetime name that appears in the defining-type-id of an **alias |
| declaration** must either be `static` or one of the lifetime parameters |
| declared in a `lifetime_param` attribute on the alias declaration. Note that |
| if the alias declaration is nested within a class that also has lifetime |
| parameters, those lifetime parameters may *not* appear in the |
| defining-type-id of the alias declaration. |
| |
| ## Appendix B: <code>std::string_view</code> with lifetime annotations {#appendix-b-std-string_view-with-lifetime-annotations} |
| |
| To illustrate how lifetime annotations work on a larger code sample, here is an |
| annotated version of interesting parts of `std::string_view`. To keep the code |
| clear, we have omitted basic\_string\_view and simply stamped out `string_view` |
| for the template arguments used in its definition. |
| |
| ```c++ |
| // Lifetime "s" is mnemonic for "lifetime parameter of string_view" |
| class LIFETIME_PARAM(s) string_view { |
| public: |
| using const_pointer LIFETIME_PARAM(iter_lifetime) = const char *$(iter_lifetime); |
| using const_reference LIFETIME_PARAM(iter_lifetime) = const char &$(iter_lifetime); |
| using const_iterator LIFETIME_PARAM(iter_lifetime) = const char *$(iter_lifetime); |
| using iterator LIFETIME_PARAM(iter_lifetime) = const_iterator $(iter_lifetime); |
| using const_reverse_iterator LIFETIME_PARAM(iter_lifetime) = |
| std::reverse_iterator<const_iterator $(iter_lifetime)>; |
| using reverse_iterator LIFETIME_PARAM(iter_lifetime) = |
| const_reverse_iterator $(iter_lifetime); |
| |
| using size_type = size_t; |
| |
| static constexpr size_type npos = static_cast<size_type>(-1); |
| |
| constexpr string_view() noexcept; |
| constexpr string_view(const string_view $s & other) noexcept = default; |
| constexpr string_view(const char* $s data, size_type len); |
| |
| constexpr const_iterator $s begin() const noexcept; |
| constexpr const_iterator $s end() const noexcept; |
| constexpr const_reverse_iterator $s rbegin() const noexcept; |
| constexpr const_reverse_iterator $s rend() const noexcept; |
| |
| constexpr const_reference $s front() const; |
| constexpr const_reference $s back() const; |
| |
| constexpr const_pointer $s data() const noexcept; |
| |
| constexpr const_reference $s operator[](size_type i) const; |
| constexpr const_reference $s at(size_type i) const; |
| |
| // The annotation cannot express that the lifetime parameter of `this` and |
| // `other` are swapped after the call, so we have to be overly restrictive and |
| // require `this` and `other` to have the same lifetime parameter. |
| constexpr void swap(string_view $s & other) noexcept; |
| |
| // Output buffer may have a different lifetime than this string view's data. |
| size_type copy(char* buf, size_type n, size_type pos = 0) const; |
| |
| // Returned substring has the same lifetime parameter as this `string_view`. |
| constexpr string_view $s substr(size_type pos = 0, size_type n = npos) const; |
| |
| // `string_view` to compare against does not need to share the same lifetime. |
| constexpr int compare(string_view x) const noexcept; |
| |
| private: |
| const char* $s ptr_; |
| size_type length_; |
| }; |
| ``` |
| |
| <!-- Footnotes themselves at the bottom. --> |
| |
| ## Notes |
| |
| [^1]: The attribute will be scoped to some suitable namespace, but for ease of |
| exposition we assume here that it is placed in the global namespace. |
| [^2]: `lifetime_cast` will be placed in a suitable namespace, but for ease of |
| exposition, we assume here that it is in the global namespace. |
| [^3]: “`$`” is not part of the standard set of characters allowed in C++ |
| identifiers (including macro names), but the C++ standard permits |
| implementations to allow additional implementation-defined characters, and |
| gcc, Clang, and MSVC allow `$` as an implementation-defined character. |
| [^4]: More formally, this is because function types are contravariant in their |
| parameter types. |
| [^5]: For simplicity, we are showing `std::string_view` as if it was a |
| non-template type. |
| [^6]: Unless converting constructors and conversion constructors are used to |
| simulate variance. |
| [^7]: This attribute is inspired by the C++ Standards Committee paper |
| [P0936R0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0936r0.pdf). |
| [^8]: Quoting Richard Smith: "The Clang attribute behaves as if each type has |
| exactly one associated lifetime, and the attribute says in which cases the |
| outermost lifetime of a parameter matches the outermost lifetime of the |
| return value.” |
| [^9]: Note that this automatically disallows the special lifetime name `static`, |
| which is allowed in `lifetime` attributes. We make no other constraints on |
| identifiers, but codebases that want to use the lifetime annotations for |
| C++ / Rust interop may want to enforce a rule that prohibits invalid Rust |
| identifiers (e.g. Rust keywords) in the `lifetime_param` and `lifetime` |
| attributes.. |