Section: 21.3.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 nameT&
;
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 conceptsPointeeType
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).
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]:
Change Table 53 — "Reference modifications" in 21.3.8.3 [meta.trans.ref] as indicated:
Table 53 — Reference modifications Template Comments …
template <class T>
struct
add_lvalue_reference;If T
names an objecttype
or ifT
names a function type that does not have
cv-qualifiers or a ref-qualifier then the member typedeftype
shall nameT&
; otherwise, ifT
names a type "rvalue reference toT1
" then
the member typedeftype
shall nameT1&
; otherwise,type
shall nameT
.template <class T>
struct
add_rvalue_reference;If T
names an objecttype
or ifT
names a function type that does not have
cv-qualifiers or a ref-qualifier then the member typedeftype
shall nameT&&
; otherwise,type
shall nameT
. [Note: This rule reflects
the semantics of reference collapsing (9.3.4.3 [dcl.ref]). For example, when a typeT
names a typeT1&
, the typeadd_rvalue_reference<T>::type
is not an
rvalue reference. — end note]Change Table 56 — "Pointer modifications" in 21.3.8.6 [meta.trans.ptr] as indicated:
Table 56 — Pointer modifications Template Comments …
template <class T>
struct add_pointer;The member typedeftype
shall name the same type as
IfT
names a function type that has cv-qualifiers or a ref-qualifier
then the member typedeftype
shall nameT
; otherwise, it
shall name the same type asremove_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 thatadd_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.
Change Table 53 — "Reference modifications" in 21.3.8.3 [meta.trans.ref] as indicated:
Template | Comments |
---|---|
…
|
|
template <class T>
|
If T names then the member typedef type shall name T& ; otherwise, T names a type "rvalue reference to T1 " thenthe 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>
|
If T names then the member typedef type shall name T&& ; otherwise, type shall name T . [Note: This rule reflectsthe 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 anrvalue reference. — end note] |
Change Table 56 — "Pointer modifications" in 21.3.8.6 [meta.trans.ptr] as indicated:
Template | Comments |
---|---|
…
|
|
template <class T>
|
If T names a referenceable type or a (possibly cv-qualified) void type thentype shall name the same type asremove_reference_t<T>* ; otherwise, type shall name T .
|