--- breaks: false --- <style type="text/css"> ins { color: #080 } s { color: #F00 } blockquote.part { color: inherit !important } tbody code { background-color: inherit !important } </style> <table><tbody> <tr><th>Doc. no.:</th> <td>P1158R0</td></tr> <tr><th>Date:</th> <td>2018-07-11</td></tr> <tr><th>Audience:</th> <td>EWG</td></tr> <tr><th>Reply-to:</th> <td>Zhihao Yuan &lt;zy at miator dot net&gt;</td></tr> </tbody></table> # Concept-defined placeholder types ```c++ BidirectionalIterator T; T it = foo(); ``` ## Motivation We have three kinds of placeholder types to define variables -- `auto`, `decltype(auto)`, and `ClassTemplate`. The former two deduce arbitrary types, and the last one deduces specializations. So that we can constrain the deduced type to be a pointer type with `auto*`, or we can constrain the deduced type to be a specialization of `std::iterator`, but we are not able to constrain the deduced type to be an `Iterator`. We need the functionality to deduce types that satisfy the constraints expressed with Concepts. ## Introduction The following example from P0915R0[^p0915] shows a real issue when missing such a kind of constraints when declaring variables in generic code: ```c++ template <typename Producer> void uploadToGPU(Producer& producer) { auto item = producer.next(); gpuMemcpy(dst, &item, sizeof(item)); // potentially UB } ``` A quick and dirty fix is to use `static_assert`: ```c++ template <typename Producer> void uploadToGPU(Producer& producer) { auto item = producer.next(); static_assert(StandardLayoutType<decltype(item)>); gpuMemcpy(dst, &item, sizeof(item)); } ``` which comes with a considerable distance between our idea and the code we written. Here is a different workaround: ```c++ template <typename Producer> void uploadToGPU(Producer& producer) { auto item = producer.next(); StandardLayoutObject(item); gpuMemcpy(dst, &item, sizeof(item)); } template <StandardLayoutType T> void StandardLayoutObject(T) {}; ``` which is getting close. Look at this `T`, it is - used in place of a type for deduction, - constrained. It has all the functionalities we are [motivated](#Motivation) to add. The only issue is that it is a _constrained-parameter_, declared only in _template-parameter-list_. Can we introduce such a `T` in other places? ```c++ template <typename Producer> void uploadToGPU(Producer& producer) { StandardLayoutType T; T item = producer.next(); gpuMemcpy(dst, &item, sizeof(item)); } ``` That is the feature we propose -- a _constrained-type-name_. ## Design Decisions Model everything after _constrained-parameter_. 1. In a _constrained-type-name_'s scope, the _constrained-type-name_ can get involved in deduction multiple times, and must deduce to the same type. ```c++ template <Iterator T> void foo(T, std::move_iterator<T>); Iterator T; T it = begin(x); // ... std::move_iterator<T> i2 = ...; ``` An rvalue reference to _constrained-type-name_ is not a forwarding reference, because in ```c++ template <Copyable T> void foo(T&&); ``` The `foo` is invented rather than intended. Class template argument deduction can model this intention better: ```c++ Copyable T; T &&a = 'a'; template <Copyable T> struct Foo { Foo(T&&); }; Foo('a') ``` A _constrained-type-name_ is not deduced from a discarded statement in a template entity. ```c++ template <auto> auto foo() { Copyable T; if constexpr (cond) T v = ...; else T u = ...; std::aligned_storage_t<sizeof(T), alignof(T)> s; ... } ``` In the code above, `T` is only deduced from the _true_ branch. 2. A _constrained-type-name_ is a concrete type in non-deduced context after it has been deduced; if not deduced, the program is ill-formed. ```c++ template <Copyable T> void foo(std::array<char, sizeof(T)> a); foo({ 'a', 'b' }); // ill-formed Copyable T; std::array<char, sizeof(T)> a; // ill-formed ``` A _constrained-type-name_ is not deduced from a local class scope that is nested to the scope where the _constrained-type-name_ is declared. ```c++ Copyable T; auto f = [](char* p, size_t sz) { T x = foo(p, sz); // ill-formed if T is not deduced elsewhere }; ``` 3. Deducing a _constrained-type-name_ must use the copy-initialization syntax. The code that involves _constrained-type-name_ cannot be visually distinguished from initializations using concrete types, but we can limit the points of deduction by enforcing an `=` in front of the _initializer-clause_. Meanwhile, it simplifies the semantics because the function parameters are also copy-initialized when deducing the function template parameters. ```c++ template <Iterator T> void foo(T); char s[] = ""; foo(s); Iterator T; T p = s; T np(nullptr); // initializing char* foo(T(nullptr)); // ok T ep = nullptr; // ill-formed, T has been deduced ``` 4. A _constrained-type-name_ should only appear in block scope and have no linkage. In class scopes, there is a complete analysis[^n3897] to show why we will not have placeholder types on class members. In namespace scopes, _constrained-type-name_ itself will have ODR issues as soon as it gains linkage. ## Technical Description > _simple-type-specifier_:<br/> > &nbsp;&nbsp;&nbsp;&nbsp;[...]<br/> > &nbsp;&nbsp;&nbsp;&nbsp;`auto`<br/> > &nbsp;&nbsp;&nbsp;&nbsp;_decltype-specifier_<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_constrained-type-name_++<br/> > > ++_constrained-type-name_:++<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_identifier_++ > _declaration_:<br/> > &nbsp;&nbsp;&nbsp;&nbsp;[...]<br/> > &nbsp;&nbsp;&nbsp;&nbsp;_attribute-declaration_<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_constrained-type-declaration_++<br/> > > ++_constrained-type-declaration_:++<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_constrained-type-declarator-list_ `;`++<br/> > > ++_constrained-type-declarator-list_:++<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_constrained-type-declarator_++<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_constrained-type-declarator-list_ `,` _constrained-type-declarator_++<br/> > > ++_constrained-type-declarator_:++<br/> > &nbsp;&nbsp;&nbsp;&nbsp;++_qualified-concept-name_ _identifier_++<br/> > > A _constrained-type-declaration_ declares each _identifier_ that a _constrained-type-declarator_ ends with to be a _constrained-type-name_. A _constrained-type-declaration_ shall only appears at block scope. > > A _constrained-type-name_ is looked up as a _type-name_ in its scope. In an initializing declaration of a variable, Let $D$ be a sequence of the _decl-specifiers_ that are _type-specifiers_ in the _decl-specifier-seq_. If $D$ mentions a _constrained-type-name_ defined in the same scope and $D$ followed by the _declarator_ can form a function template argument in deduced context, this is a _constrained initializing declaration_, and after each _declarator_ there shall be an _initializer-clause_ followed by `=`. Let $I_1$, $I_2$, ..., $I_n$ be the list of _declarators_, $E_1$, $E_2$, ..., $E_n$ be those _initializer-clauses_ in order, $C_1$, $C_2$, ..., $C_m$ be the _constrained-type-declarators_ of all the mentioned _constrained-type-names_. Given the following invented class template, > > `template<`$C_1$, $C_2$, ..., $C_m$`>`<br/> > `struct f { f(`$D$ $I_1$, $D$ $I_2$, ..., $D$ $I_n$`); };` > > the _constrained-type-names_ are _bounded_ to be the types of the template arguments that are deduced from the call `f(`$E_1$, $E_2$, ..., $E_n$`)` as an unevaluated operand. If the class template or the call is ill-formed, the program is ill-formed. > > Any use of a _constrained-type-name_ refers to the bounded type; if the _constrained-type-name_ is not bounded at the point of use or bounded to more than one type, the program is ill-formed. Examples (not concerning forwarding references): Given ```c++ Copyable T; Iterator A, Copyable B; ``` for ```c++ T a[] = "meow"; ``` we form ```c++ template<Copyable T> void f(T a[]); ``` Calling `f("meow")` deduces `T` to `char`, so the original declaration becomes ```c++ char a[] = "meow"; ``` For ```c++ tuple<T*, int> b; tuple<A, B> &r = b, c = tuple(a, 3); ``` we form ```c++ template<Iterator A, Copyable B> void f(tuple<A, B> &r, tuple<A, B> c); ``` Calling `f(b, tuple(a, 3))` gives `A = char*` and `B = int`. ## Comparing with Other Proposals There have been a few proposals that can address the motivation of this paper: Concepts TS[^n4674], in-place syntax[^p0745], constrained `auto`[^p0915], and YAACD[^p1141]. The in-place syntax extends the _constrained-type-specifier_ from the Concepts TS by allowing optional in-place type names, so I will call their common parts "_constrained-type-specifier_" and discuss the "in-place syntax" separately. YAACD part 1 and constrained `auto` differ only in syntax so that I will group them into "constrained `auto`." The part 2 makes the syntax compatible with the simple case (a single _decl-specifier_ that is a _qualified-concept-name_) of Concepts TS so that I will skip this part. The _constrained-type-specifier_ has two set of rules for deduction, one for simple cases, ```c++ Copyable &&a = foo(); ``` and one for complex cases: ```c++ tuple<Iterator, Copyable> c = make_tuple(a, 3); ``` In simple cases, deduction result backfills the type for the variable, so `&&` is treated as a forwarding reference; in complex cases, deduction result backfills the template parameters, so `&&` is treated as an rvalue reference. This proposal, "concept-defined placeholder types" do not make this distinction and stick with the latter rule. The other constrained `auto` proposals only handle the simple cases and use the former rule. The in-place syntax and concept-defined placeholder types can naturally express consistent binding[^p0694], ```c++ // in-place syntax Copyable{T} a = foo(); tuple<Copyable{T}> b = bar(); // this paper Copyable T; T a = foo(); tuple<T> b = bar(); ``` _constrained-type-specifier_ and constrained `auto` have no such flexibility. The introduced type names can serve other purposes, for example, in ```c++ Copyable{T} &&a = foo(); ``` this `T` can replace `std::remove_cvref_t<decltype(a)>`. The difference is that, in this proposal, introducing these type names are mandatory. By doing so, we solved, simultaneously, the confusion (examples in (p)3.6[^p0694]) >> Independent resolution breaks the fundamental equivalence of the notations. and the dilemma[^p0956] >> We need a way of expressing "same type" for two uses of a concept.<br/> >> We need a way of saying "different type" for two uses of a concept. raised in Bjarne's papers. The concept-defined placeholder types require a specific form of initialization (copy-initialization) to trigger deduction. But it is hard to say whether it is a caveat or a feature, considering that none of the proposals attempt class template argument deduction from partially-specialized template argument lists[^p1021], which is implied by direct-initialization. ## Extensions If we add multi-argument _constrained-parameters_[^p1157], ```c++ template <EqualityComparableWith T U> void foo(T const& a, U const& b) { if (a == b) // must be valid ``` we should also allow declaring multiple _constrained-type-names_ that satisfy a multi-parameter concept at once: ```c++ EqualityComparableWith T U; T a = /* ... */; U b = /* ... */; if (a == b) // must be valid ``` We may also want to add parameter pack support. ## References [^p0915]: Romeo, Vittorio, and John Lakos. P0915R0 _Concept-constrained auto_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0915r0.html> [^n3897]: Voutilainen, Ville. N3897 _Auto-type members_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3897.html> [^n4674]: Sutton, Andrew. N4674 _Working Draft, C++ extensions for Concepts_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4674.pdf> [^p0745]: Sutter, Herb. P0745R1 _Concepts in-place syntax_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0745r1.pdf> [^p1141]: Voutilainen, Ville, et al. P1141R0 _Yet another approach for constrained declarations_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1141r0.html> [^p0694]: Stroustrup, Bjarne. P0694R0 _Function declarations using concepts_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0694r0.pdf> [^p0956]: Stroustrup, Bjarne. P0956R0 _Answers to concept syntax suggestions_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0956r0.pdf> [^p1157]: Yuan, Zhihao. P1157R0 _Multi-argument constrained-parameter_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1157r0.html> [^p1021]: Spertus, Mike. P1021R0 _Extensions to Class Template Argument Deduction_. <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1021r0.html>