You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<span id="cb91-9"><a href="#cb91-9" aria-hidden="true" tabindex="-1"></a><span class="co">// If the complete template was chosen (but it won't be), you'd get:</span></span>
and <code class="sourceCode cpp"><span class="dt">int</span><span class="op">^/</span><span class="er">#T1</span></code>
4768
+
are different types, but after being stripped, you’re left with <code class="sourceCode cpp"><span class="dt">int</span><span class="op">^</span></code>
4769
+
and <code class="sourceCode cpp"><span class="dt">int</span><span class="op">^</span></code>
4770
+
which are the same type. They’ll match the partial specialization when
4771
+
that’s also stripped of its proxy lifetimes.</p>
4772
+
<p>The template arguments of the partial or explicit specialization have
4773
+
different lifetimes than the template arguments of the complete
4774
+
template. But this should be expected: the template arguments of partial
4775
+
or explicit specializations are already different than the primary
4776
+
template’s. Outside of the template definition, users always refer to
4777
+
the specialization through the template arguments on the complete
4778
+
template. That remains the case in this modification.</p>
4779
+
<p>If a partial or explicit specialization is chosen, that could be
4780
+
regarded as a loss of information, since two invented lifetime
4781
+
parameters that were independent get collapsed into one. Fortunately
4782
+
this doesn’t compromise safety since borrow checking is performed after
4783
+
instantiation on complete templates. It was misguided for me to worry
4784
+
about the loss of lifetime independence that would result from partial
4785
+
specialization, since why else would the class provide those
4786
+
specializations if they didn’t want them to be used?</p>
4787
+
<p>We should also revise the policy for using lifetime parameters in
4788
+
class definitions. Named lifetimes must be used by non-static data
4789
+
members, or else the definition is ill-formed. Template lifetime
4790
+
parameters, which are implicit, need not be used by non-static data
4791
+
members. These are <em>unbound lifetimes</em>, and that should be okay.
Copy file name to clipboardexpand all lines: proposal/draft.md
+31
Original file line number
Diff line number
Diff line change
@@ -1821,6 +1821,8 @@ A class template's instantiation doesn't depend on the lifetimes of its users. `
1821
1821
1822
1822
In the current safety model, this transformation only occurs for bound lifetime template parameters with the `typename T+` syntax. It's not done for all template parameters, because that would interfere with C++'s partial and explicit specialization facilities.
1823
1823
1824
+
**(See [post-submission note](#post-submission-developments) from November 2024)**
1825
+
1824
1826
```cpp
1825
1827
template<typename T0, typename T1>
1826
1828
struct is_same {
@@ -2740,6 +2742,35 @@ It's already possible to write C++ code that is less burdened by cleanup paths t
2740
2742
2741
2743
This extended relocation feature is some of the ripest low-hanging fruit for improving the safety experience in Safe C++.
2742
2744
2745
+
# Post-submission developments
2746
+
2747
+
**November 2024:**
2748
+
2749
+
The treatment of lifetime binder template parameters can be greatly simplified by dropping the [`<typename T+>`](#lifetimes-and-templates) syntax and _always_ generating lifetime template parameters when used in class or function templates. This is discussed in a [Github Issue](https://github.com/cppalliance/safe-cpp/issues/12).
2750
+
2751
+
We can walk through the formerly problematic case of `std::is_same`. We want the partial selected even when the specialization's template arguments have lifetime binders.
2752
+
2753
+
```cpp
2754
+
std::is_same<int^, int^>::value;
2755
+
2756
+
// Implicitly add placeholders to unbound binders in class template arguments.
2757
+
// The above transforms to:
2758
+
std::is_same<int^/_, int^_>::value;
2759
+
2760
+
// During normalization, replace lifetime arguments with invented
2761
+
// template lifetime parameters.
2762
+
// If the complete template was chosen (but it won't be), you'd get:
2763
+
std::is_same<int^/#T0, int^/#T1>/_/_::value
2764
+
```
2765
+
2766
+
The `is_same` specialization is created with two _proxy lifetimes_. When a complete type is needed the compiler searches for the best partial or explicit specialization. This will now involve stripping all lifetime binders. `int^/#T0` and `int^/#T1` are different types, but after being stripped, you're left with `int^` and `int^` which are the same type. They'll match the partial specialization when that's also stripped of its proxy lifetimes.
2767
+
2768
+
The template arguments of the partial or explicit specialization have different lifetimes than the template arguments of the complete template. But this should be expected: the template arguments of partial or explicit specializations are already different than the primary template's. Outside of the template definition, users always refer to the specialization through the template arguments on the complete template. That remains the case in this modification.
2769
+
2770
+
If a partial or explicit specialization is chosen, that could be regarded as a loss of information, since two invented lifetime parameters that were independent get collapsed into one. Fortunately this doesn't compromise safety since borrow checking is performed after instantiation on complete templates. It was misguided for me to worry about the loss of lifetime independence that would result from partial specialization, since why else would the class provide those specializations if they didn't want them to be used?
2771
+
2772
+
We should also revise the policy for using lifetime parameters in class definitions. Named lifetimes must be used by non-static data members, or else the definition is ill-formed. Template lifetime parameters, which are implicit, need not be used by non-static data members. These are _unbound lifetimes_, and that should be okay. The lifetimes in the specialization of `std::is_same` don't mean anything, and can be safely ignored.
2773
+
2743
2774
# Implementation guidance
2744
2775
2745
2776
The intelligence behind the _ownership and borrowing_ safety model resides in the compiler's middle-end, in its _MIR analysis_ passes. The first thing compiler engineers should focus on when pursuing memory safety is to lower their frontend's AST to MIR. Several compiled languages already pass through a mid-level IR: Swift passes through SIL,[@sil] Rust passes through MIR,[@mir] and Circle passes through it's mid-level IR when targeting the new object model. There is an effort called ClangIR[@clangir] to lower Clang to an MLIR dialect called CIR, but the project is in an early phase and doesn't have enough coverage to support the language or library features described in this document.
0 commit comments