Consider the following code: (https://godbolt.org/z/8W699x6q6)
int* p;
const int*&& r = static_cast<int*&&>(p);
Note: const int*&&
is an rvalue reference to a pointer to const int
.
Clang compiles this, and r
binds to a temporary object:
p: .quad 0
r: .quad _ZGR1r_ // r is a reference to a temporary object, otherwise this would be p
GCC rejects this code:
<source>:2:18: error: binding reference of type 'const int*&&' to 'int*' discards qualifiers
2 | const int *&&r = static_cast<int*&&>(p);
| ^~~~~~~~~~~~~~~~~~~~~~
Personally, I think GCC correctly implements the changes of CWG 2352 to [dcl.init.ref] p4 but I'm not confident that I'm interpreting things right. Which compiler is correct here?
Note: the example in this question is inspired by the last line of code mentioned in CWG 2018.
Note: if it was allowed to bind const int*&&
to int*&&
, this would present a const-correctness footgun. It's the same issue as converting int**
to const int**
. Personally, I think it's unlikely that the committee wants this reference binding to be allowed, however, maybe the wording still allows for it despite the defect report.
Under the definitions given in [dcl.init.ref]/4,
const int
" is not reference-compatible with "pointer to int
", because int**
is not convertible to const int**
.const int
" is reference-related to "pointer to int
" because these two types are similar.Reference-compatibility governs when a direct binding is possible. Such a direct binding must respect const-correctness: if an int*
object p
could be referred to via a reference r
to non-const const int*
, then the value of a const int*
could be written through r
, thus copying its value into p
. Thus, this direct binding is not allowed. However, if the referenced type is itself const (that is, the referenced type is const int* const
) then it is reference-compatible with int*
under [conv.qual]/3. (Bullet 3.3 requires the extra const
.)
If the necessary reference-compatible relationship existed, this reference binding would be governed by [dcl.init.ref]/5.3. In this case, because the referenced type is not reference-compatible with the initializer's type, we instead fall through to [dcl.init.ref]/5.4.2, which requires a temporary object of type const int*
to be created; the reference binds to that object and not the initializer.
I agree with the OP that this outcome seems unintended. CWG2018 referred to the following indirect reference bindings as "weird":
const int* const
binding to int*
const int*
binding to int*
The former was made a direct reference binding by CWG2352. The latter cannot be a direct binding because such a direct binding would violate const correctness, so it should be ill-formed.
A further rationale for making this reference binding ill-formed is analogy with:
const int x = 0;
int&& r = std::move(x);
This does not make a copy of x
and bind the reference to the temporary; it is ill-formed. The reason why the creation of a temporary isn't permitted in this case is that the referenced type is "too close" to the initializer's type: namely, the two types are reference-related. The reference-relatedness causes p5.4.3 to kick in:
cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; [...]
CWG2352 extended reference-relatedness to cases like that given by the OP. It seems likely that an oversight in the drafting led to the current inconsistency.
Newly created CWG2801 proposes wording to rectify the issue, making the OP's code ill-formed.