This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.

### 2101. Some transformation types can produce impossible types

Section: 20.15.8 [meta.trans] Status: C++17 Submitter: Daniel Krügler Opened: 2011-11-18 Last modified: 2017-07-30

Priority: 3

View all issues with C++17 status.

Discussion:

Table 53 — "Reference modifications" says in regard to the type trait `add_lvalue_reference` (emphasize mine)

> If `T` names an object or function type then the member typedef type shall name `T&`;

The problem with this specification is that function types with cv-qualifier or ref-qualifier, like

>     void() const
>     void() &

are also affected by the first part of the rule, but this would essentially mean, that instantiating `add_lvalue_reference` with such a type would attempt to form a type that is not defined in the C++ type system, namely

>     void(&)() const
>     void(&)() &

The general policy for TransformationTraits is to define always some meaningful mapping type, but this does not hold for `add_lvalue_reference`, `add_rvalue_reference`, and in addition to these two for `add_pointer` as well. The latter one would attempt to form the invalid types

>     void(*)() const
>     void(*)() &

A possible reason why those traits were specified in this way is that in C++03 (and that means for TR1), cv-qualifier were underspecified in the core language and several compilers just ignored them during template instantiations. This situation became fixed by adopting CWG issues 295 and 547.

While there is possibly some core language clarification needed (see reflector messages starting from c++std-core-20740), it seems also clear that the library should fix the specification. The suggested resolution follows the style of the specification of the support concepts `PointeeType` and `ReferentType` defined in N2914.

[2012-02-10, Kona]

Move to NAD.

These cv- and ref-qualified function types are aberrations in the type system, and do not represent any actual entity defined by the language. The notion of cv- and ref- qualification applies only to the implicit `*this` reference in a member function.

However, these types can be produced by quirks of template metaprogramming, the question remains what the library should do about it. For example, `add_reference` returns the original type if passed a reference type, or a `void` type. Conversely, `add_pointer` will return a pointer to the referenced type when passed a reference.

It is most likely that the 'right' answer in any case will depend on the context that the question is being asked, in terms of forming these obscure types. The best the LWG can do is allow an error to propagate back to the user, so they can provide their own meaningful answer in their context - with additional metaprogramming on their part. The consensus is that if anyone is dangerous enough with templates to get themselves into this problem, they will also have the skills to resolve the problem themselves. This is not going to trip up the non-expert developer.

Lastly, it was noted that this problem arises only because the language is inconsistent in providing us these nonsense types that do no really represent anything in the language. There may be some way Core or Evolution could give us a more consistent type system so that the LWG does not need to invent an answer at all, should this question need resolving. This is another reason to not specify anything at the LWG trait level at this time, leaving the other working groups free to produce the 'right' answer that we can then follow without changing the meaning of existing, well-defined programs.

[2012-02-10, post-Kona]

Move back to Open. Daniel is concerned that this is not an issue we can simply ignore, further details to follow.

[2012-10-06, Daniel comments]

This issue really should be resolved as a defect: First, the argument that "forming these obscure types" should "allow an error to propagate" is inconsistent with the exact same "obscure type" that would be formed when `std::add_lvalue_reference<void>` wouldn't have an extra rules for `void` types, which also cannot form references. The originally proposed resolution attempts to apply the same solution for the same common property of `void` types and function types with cv-qualifiers or ref-qualifier. These functions had the property of `ReferentType` during concept time (see CWG 749 bullet three for the final wording).

Core issue CWG 1417 has clarified that any attempt to form a reference of a pointer to a function type with cv-qualifiers or ref-qualifier is ill-formed. Unfortunately, many compilers don't implement this yet.

I also would like to warn about so-called "obscure" types: The problem is that these can occur as the side effect of finding a best match overload of function templates, where this type is exactly correct for one of these overloads, but causes a deep (not-sfinae-friendly) error for others where one of these traits are part of the signature.

Existing experience with `void` types shows, that this extra rule is not so unexpected. Further, any usage of the result types of these traits as argument types or return types of functions would make these ill-formed (and in a template context would be sfinaed away), so the expected effects are rarely unnoticed. Checking all existing explicit usages of the traits `add_rvalue_reference`, `add_lvalue_reference`, and `add_pointer` didn't show any example where the error would be silent: `add_rvalue_reference` is used to specify the return value of `declval()` and the instantiation of `declval<void() const>()` would be invalid, because of the attempt to return a function type. Similarly, `add_lvalue_reference` is used to specify the return type of `unique_ptr<T>::operator*()`. Again, any instantiation with `void() const` wouldn't remain unnoticed. The trait `add_pointer` is used to specify the trait `std::decay` and this is an interesting example, because it is well-formed when instantiated with `void` types, too, and is heavily used throughout the library specification. All use-cases would not be negatively affected by the suggested acceptance of function types with cv-qualifiers or ref-qualifier, because they involve types that are either function arguments, function parameters or types were references are formed from.

The alternative would be to add an additional extra rule that doesn't define a type member 'type' when we have a function type with cv-qualifiers or ref-qualifier. This is better than the current state but it is not superior than the proposal to specify the result as the original type, because both variants are sfinae-friendly. A further disadvantage of the "non-type" approach here would be that any usage of `std::decay` would require special protection against these function types, because instantiating `std::decay<void() const>` again would lead to a deep, sfinae-unfriendly error.

The following example demonstrates the problem: Even though the second `f` template is the best final match here, the first one will be instantiated. During that process `std::decay<T>::type` becomes instantiated as well and will raise a deep error, because as part of the implementation the trait `std::add_pointer<void() const>` becomes instantiated:

>     #include <type_traits>

>     template<class T>
>     typename std::decay<T>::type f(T&& t);

>     template<class T, class U>
>     U f(U u);

>     int main() {
>       f<void() const>(0);
>     }

When the here proposed resolution would be applied this program would be well-formed and selects the expected function.

Previous resolution from Daniel [SUPERSEDED]:

> 1. Change Table 53 — "Reference modifications" in 20.15.8.3 [meta.trans.ref] as indicated:

>    Table 53 — Reference modifications
>    Template | Comments
>    --- | ---
>    `…`
>    `template <class T> struct add_lvalue_reference;` | If `T` names an object `type` or if `T` names a function type that does not have cv-qualifiers or a ref-qualifier then the member typedef `type` shall name `T&`; otherwise, if `T` names a type "rvalue reference to `T1`" then the member typedef `type` shall name `T1&`; otherwise, `type` shall name `T`.
>    `template <class T> struct add_rvalue_reference;` | If `T` names an object `type` or if `T` names a function type that does not have cv-qualifiers or a ref-qualifier then the member typedef `type` shall name `T&&`; otherwise, `type` shall name `T`. [Note: This rule reflects the semantics of reference collapsing (9.3.4.3 [dcl.ref]). For example, when a type `T` names a type `T1&`, the type `add_rvalue_reference<T>::type` is not an rvalue reference. — end note]

> 2. Change Table 56 — "Pointer modifications" in 20.15.8.6 [meta.trans.ptr] as indicated:

>    Table 56 — Pointer modifications
>    Template | Comments
>    --- | ---
>    `…`
>    `template <class T> struct add_pointer;` | The member typedef `type` shall name the same type as If `T` names a function type that has cv-qualifiers or a ref-qualifier then the member typedef `type` shall name `T`; otherwise, it shall name the same type as `remove_reference<T>::type*`.

The following revised proposed resolution defines - in the absence of a proper core language definition - a new term referenceable type as also suggested by the resolution for LWG 2196 as an umbrella of the negation of `void` types and function types with cv-qualifiers or ref-qualifier. This simplifies and minimizes the requires wording changes.

[ 2013-09-26, Daniel synchronizes wording with recent draft ]

[ 2014-05-18, Daniel synchronizes wording with recent draft and comments ]

My impression is that this urgency of action this issue attempts to point out is partly caused by the fact that even for the most recent C++14 compilers the implementations have just recently changed to adopt the core wording. Examples for these are bug reports to gcc or clang.

Occasionally the argument has been presented to me that the suggested changes to the traits affected by this issue would lead to irregularities compared to other traits, especially the lack of guarantee that `add_pointer` might not return a pointer or that `add_(l/r)value_reference` might not return a reference type. I would like to point out that this kind of divergence is actually already present in most `add/remove` traits: For example, we have no guarantee that `add_const` returns a const type (Reference types or function types get special treatments), or that `add_rvalue_reference` returns an rvalue-reference (e.g. when applied to an lvalue-reference type).

Zhihao Yuan brought to my attention, that the originally proposing paper N1345 carefully discussed these design choices.

[2015-05, Lenexa]

MC: move to Ready: in favor: 16, opposed: 0, abstain: 1
STL: have libstdc++, libc++ implemented this? we would need to change your implementation

Proposed resolution:

This wording is relative to N3936.

1. Change Table 53 — "Reference modifications" in 20.15.8.3 [meta.trans.ref] as indicated:

   Table 53 — Reference modifications
   Template | Comments
   --- | ---
   `…`
   `template <class T> struct add_lvalue_reference;` | If `T` names an object or function type a referenceable type then the member typedef `type` shall name `T&`; otherwise, if `T` names a type "rvalue reference to `T1`" then the member typedef `type` shall name `T1&`; otherwise, `type` shall name `T`. [Note: This rule reflects the semantics of reference collapsing (9.3.4.3 [dcl.ref]). — end note]
   `template <class T> struct add_rvalue_reference;` | If `T` names an object or function type a referenceable type then the member typedef `type` shall name `T&&`; otherwise, `type` shall name `T`. [Note: This rule reflects the semantics of reference collapsing (9.3.4.3 [dcl.ref]). For example, when a type `T` names a type `T1&`, the type `add_rvalue_reference_t<T>` is not an rvalue reference. — end note]

2. Change Table 56 — "Pointer modifications" in 20.15.8.6 [meta.trans.ptr] as indicated:

   Table 56 — Pointer modifications
   Template | Comments
   --- | ---
   `…`
   `template <class T> struct add_pointer;` | If `T` names a referenceable type or a (possibly cv-qualified) `void` type then T the member typedef `type` shall name the same type as `remove_reference_t<T>*`; otherwise, `type` shall name `T`.