LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 44688 - Bogus reference bindings incorrectly accepted
Summary: Bogus reference bindings incorrectly accepted
Status: NEW
Alias: None
Product: clang
Classification: Unclassified
Component: C++ (show other bugs)
Version: trunk
Hardware: PC Windows NT
: P enhancement
Assignee: Unassigned Clang Bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-01-27 15:59 PST by Casey Carter
Modified: 2020-01-30 19:30 PST (History)
5 users (show)

See Also:
Fixed By Commit(s):


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Casey Carter 2020-01-27 15:59:01 PST
Today's trunk and release/10.x accept the TU:

  struct ExplicitToInt {
    explicit operator int() const;
  };

  const int& x(ExplicitToInt{}); // #1
  int&& y(ExplicitToInt{});      // #2

despite that GCC, MSVC, and I agree that both reference bindings are ill-formed. Running quickly through the bullets in [dcl.init.ref]/5:

* 5.1 doesn't apply: the initializer is not an lvalue

* 5.2 doesn't apply: neither target reference is an lvalue reference to non-const-qualified or volatile-qualified type

* 5.3.1 doesn't apply: Neither int nor const int is reference-compatible with ExplicitToInt

* 5.3.2: doesn't apply, ***assuming that "can be converted" means "can be implicitly converted"***

* 5.4: doesn't apply because the explicit conversion operator cannot be selected for "copy-initialization of an object of type "cv1 T1" [const int and int for #1 and #2 respectively] by user-defined conversion".
Comment 1 Richard Smith 2020-01-27 18:47:24 PST
(In reply to Casey Carter from comment #0)
> * 5.3.2: doesn't apply, ***assuming that "can be converted" means "can be
> implicitly converted"***

It doesn't exactly mean that. It has a cross-reference to 12.4.1.6 [over.match.ref], which contains the rules in scope here; those rules *do* permit using an explicit conversion function in the context of direct-initialization. However, those rules only consider conversion functions that convert to a reference type. So:

  struct ExplicitToInt {
    explicit operator int&() const;
    explicit operator int&&() const;
  };

  const int& x(ExplicitToInt{});
  int&& y(ExplicitToInt{});

... is valid, per /5.3.2. But the original testcase is not. I would imagine we're not restricting to conversion functions that convert to reference types in this case.


This is only invalid due to a recent rule change (DR2267) which Clang doesn't yet implement.
Comment 2 Casey Carter 2020-01-27 19:49:17 PST
Ah, thank you for the clarification about following the cross-reference to [over.match.ref]. I *knew* there had to be a reason that 5.1.2 and 5.3.2 simply say "can be converted" vs. 5.4.2's "is implicitly converted".
Comment 3 Richard Smith 2020-01-28 13:26:31 PST
Oh, this is unfortunate... implementing the DR2267 rule change breaks the example from DR1758:

struct X { X(); };
struct Y { explicit operator X(); } y;
X x{y};

(in pre-C++17 language modes) because we can no longer convert y when calling the move constructor of X.

Also of note: it turns out that MSVC and ICC don't reject the example in comment#0 if the target type of the conversion is a class type. (Clang's current behavior is consistent between classes and non-classes, but being consistently wrong isn't worth much.)

We should find a way to reject the comment#1 example (for both class and non-class destination types) without undoing DR1758 for C++11 and C++14. GCC manages it somehow.
Comment 4 Richard Smith 2020-01-30 19:30:37 PST
Turns out that DR1758 is not actually broken, but fixing DR2267 exposes a longstanding infelicity in how we implement the rule that explicit conversion functions can sometimes be used when initializing a temporary bound to a copy/move constructor's reference parameter. Some yak shaving is required here...