From 53bf25cdad7cd9417105bb90889a65e1bc15c364 Mon Sep 17 00:00:00 2001 From: timsong-cpp Date: Wed, 11 Nov 2020 21:15:51 -0600 Subject: [PATCH] wip --- xxxx_reference_temporary/Makefile | 4 + .../reference_temporary.md | 189 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 xxxx_reference_temporary/Makefile create mode 100644 xxxx_reference_temporary/reference_temporary.md diff --git a/xxxx_reference_temporary/Makefile b/xxxx_reference_temporary/Makefile new file mode 100644 index 0000000..82d4ec0 --- /dev/null +++ b/xxxx_reference_temporary/Makefile @@ -0,0 +1,4 @@ +include ../md/mpark-wg21.mk + +dxxxxr0.html : reference_temporary.md + $(PANDOC) diff --git a/xxxx_reference_temporary/reference_temporary.md b/xxxx_reference_temporary/reference_temporary.md new file mode 100644 index 0000000..b1301ef --- /dev/null +++ b/xxxx_reference_temporary/reference_temporary.md @@ -0,0 +1,189 @@ +--- +title: A type trait to detect reference binding to temporary +document: DXXXXR0 +date: today +audience: + - LEWG + - EWG +author: + - name: Tim Song + email: +toc: false +--- + +# Abstract + +This paper proposes adding two new type traits with compiler support +to detect when initialization a reference would bind it to a +lifetime-extended temporary, and changing several standard library +components (`pair`, `tuple` and _`INVOKE`_) to make such binding +ill-formed when it would inevitably produce a dangling reference. + +# In brief + +::: tonytable +## Before +```c++ +std::tuple x("hello"); // dangling +std::function f = [] { + return ""; +}; + +f(); // dangling +``` + +## After +```c++ +std::tuple x("hello"); // ill-formed +std::function f = [] { // ill-formed + return ""; +}; + +f(); +``` +::: + +# Motivation + +Various parts of the standard library need to initialize an entity +of some user-provided type `T` from an expression of a potentially +different type. When `T` is a reference type, this can easily create +dangling references. This occurs, for instance, when a +`std::tuple` is initialized from something convertible to it: + +```c++ +std::tuple t("meow"); +``` + +This construction _always_ creates a dangling reference, because the +`std::string` temporary is created _inside_ the selected constructor +of `tuple` (`template tuple(UTypes&&...)`), +and not outside it. Thus, unlike `string_view`'s implicit conversion +from rvalue strings, under no circumstances can this construction be +used correctly. + +Similarly, a `std::function` currently accepts any +callable whose invocation produces something convertible to +`const string&`. However, if the invocation produces a `std::string` +or a `const char*`, the returned reference would be bound to a +temporary and dangle. + +Moreover, in both of the above cases, the problematic reference binding +occurs inside the standard library's code, and several major implementations +are known to suppress warnings in such contexts. + +## Previous attempts + +[@P0932R1] proposes modifying the constraints on `std::function` to +prevent such creation of dangling references. However, the proposed +modification is incorrect (it has both false positives and false negatives), +and correctly detecting all cases in which dangling references will be +created without false positives is likely impossible or at least +heroically difficult without compiler assistance, due to the +existence of user-defined conversions. + +[@XXXX] changed the core language rules so that initialization of a reference +data member in a _mem-initializer_ is ill-formed if the initialization would +bind it to a temporary expression, which is exactly the condition these traits +seek to detect. However, this does not appear to have been widely implemented +yet, and the ill-formedness occurs outside a SFINAE context, so it is +not usable in constraints. Moreover, this requires a class with a data member of +reference type, which may not be suitable for user types that want to represent +a reference type differently (to facilitate rebinding, for instance). + +# Design decisions + +## Two type traits +We propose two traits, `reference_construction_binds_to_temporary` and +`reference_conversion_binds_to_temporary`, to cover both non-list +direct-initialization and copy-initialization. The former is useful in +classes like `std::tuple` and `std::pair` where `explicit` constructors +and conversion functions may be used; the latter is useful for _`INVOKE`_ +(i.e., `std::function`) where only implicit conversions are considered. + +As is customary in the library traits, "construct" is used to denote +direct-initialization and "convert" is used to denote copy-initialization. + +## Treat prvalues as distinct from xvalues +Unlike most library type traits, we propose that the traits handle prvalues +and xvalues differently: `reference_construction_binds_to_temporary_v` +is `true`, while `reference_construction_binds_to_temporary_v` is +`false`. This is useful for _`INVOKE`_; binding an rvalue reference +to an xvalue-returning function is not incorrect (as long as the function +does not return a dangling reference itself), but binding it to a prvalue (or +a temporary object materialized therefrom) would be in this context. + +# Implementation experience + +Clang has a `__reference_binds_to_temporary` intrinsic that partially implements the +direct-initialization variant of this trait (it does not implement the same-type +prvalue part). + +# Wording +This wording is relative to [@N4849]. + +Edit [meta.type.synop]{.sref}, header `` synopsis, as indicated: + +```diff + namespace std { + @_[...]_@ + template struct has_unique_object_representations; + ++ template struct reference_construction_binds_to_temporary; ++ template struct reference_conversion_binds_to_temporary; + + @_[...]_@ + + template + inline constexpr bool has_unique_object_representations_v + = has_unique_object_representations::value; + ++ template ++ inline constexpr bool reference_construction_binds_to_temporary_v ++ = reference_construction_binds_to_temporary::value; ++ template ++ inline constexpr bool reference_conversion_binds_to_temporary_v ++ = reference_conversion_binds_to_temporary::value; + + @_[...]_@ + } +``` + +In [meta.unary.prop]{.sref}, Table 47 [tab:meta.unary.prop], add the following: + + +| Template | Condition | Preconditions | +| --- | --- | --- | +| +```c++ +template +struct reference_construction_binds_to_temporary; +``` +| `conjunction_v, is_constructible>` is `true`, +and given an expression `E` such that `decltype((E))` is `U`, +the initialization `T t(E);` binds `E` to a temporary expression ([class.temporary]{.sref}). +|`U` shall be a complete type, _cv_ `void`, or an array of unknown bound. | +| +```c++ +template +struct reference_conversion_binds_to_temporary; +``` +| `conjunction_v, is_convertible>` is `true`, +and given an expression `E` such that `decltype((E))` is `U`, +the initialization `T t = E;` binds `E` to a temporary expression ([class.temporary]{.sref}). +|`U` shall be a complete type, _cv_ `void`, or an array of unknown bound. | + +Edit [pairs.pair]{.sref} p8 through p14 as indicated: + + + +Edit [tuple.cnstr]{.sref} as indicated: + + + +Edit [func.require]{.sref} p2 as indicated: + +[2]{.pnum} Define `INVOKE(f, t1, t2, ... , tN )` as `static_cast(INVOKE(f, t1, t2, ... , tN ))` if R is +_cv_ `void`, otherwise `INVOKE(f, t1, t2, ... , tN )` implicitly converted to `R`. +[If `reference_conversion_binds_to_temporary_v` is `true`, +`INVOKE(f, t1, t2, ... , tN )` is ill-formed. ]{.diffins}