On Mon, 30 Aug 2021, Patrick Palka wrote:

> In the context of overload resolution we have the notion of a "bad"
> argument conversion, which is a conversion that "would be a permitted
> with a bending of the language standards", and we handle such bad
> conversions specially.  In particular, we rank a bad conversion as
> better than no conversion but worse than a good conversion, and a bad
> conversion doesn't necessarily make a candidate unviable.  With the
> flag -fpermissive, we permit the situation where overload resolution
> selects a candidate that contains a bad conversion (which we call a
> non-strictly viable candidate).  And without the flag we issue a
> distinct permerror in this situation instead.
> 
> One consequence of this defacto behavior is that in order to distinguish
> a non-strictly viable candidate from an unviable candidate, if we
> encounter a bad argument conversion during overload resolution we must
> keep converting subsequent arguments because a subsequent conversion
> could render the candidate unviable instead of just non-strictly viable.
> But checking subsequent arguments can force template instantiations and
> result in otherwise avoidable hard errors.  And in particular, all
> 'this' conversions are at worst bad, so this means the const/ref-qualifiers
> of a member function can't be used to prune a candidate quickly, which
> is the subject of the mentioned PR.
> 
> This patch tries to improve the situation without changing the defacto
> output of add_candidates.  Specifically, when considering a candidate
> during overload resolution this patch makes us shortcut argument
> conversion checking upon encountering the first bad conversion
> (tentatively marking the candidate as non-strictly viable, though it
> could ultimately be unviable) under the assumption that we'll eventually
> find a strictly viable candidate anyway (rendering the distinction
> between non-strictly viable and unviable moot, since both are worse
> than a strictly viable candidate).  If this assumption turns out to be
> false, we'll fully reconsider the candidate under the defacto behavior
> (without the shortcutting).
> 
> So in the best case (there's a strictly viable candidate), we avoid
> some argument conversions and/or template argument deduction that may
> cause a hard error.  In the worst case (there's no such candidate), we
> have to redundantly consider some candidates twice.  (In a previous
> version of the patch, to avoid this redundant checking I created a new
> "deferred" conversion type that represents a conversion that is yet to
> be performed, and instead of reconsidering a candidate I just realized
> its deferred conversions.  But it doesn't seem this redundancy is a
> significant performance issue to justify the added complexity of this
> other approach.)
> 
> Lots of care was taken to preserve the defacto behavior w.r.t.
> non-strictly viable candidates, but I wonder how important this behavior
> is nowadays?  Can the notion of a non-strictly viable candidate be done
> away with, or is it here to stay?

To expand on this, as a concrete alternative to this optimistic shortcutting
trick we could maybe recognize non-strictly viable candidates only when
-fpermissive (and just mark them as unviable when not -fpermissive).  IIUC
this would be a backwards compatible change overall -- only diagnostics would
be affected, probably for the better, since we'd explain the rejection reason
for more candidates in the event of overload resolution failure.

Here's a testcase for which such a change would result in better diagnostics:

  struct A {
    void f(int, int) const; // #1
    void f(int);            // #2
  };
  
  int main() {
    const A a;
    a.f(0);
  }

We currently consider #2 to be a better candidate than #1 because the
bad conversion of the 'this' argument makes it only non-strictly
viable, whereas #1 is considered unviable due to the arity mismatch.
So overload resolution selects #2 and we end up making no mention of #1
in the subsequent diagnostic:

  <stdin>: In function ‘int main()’:
  <stdin>:8:8: error: passing ‘const A’ as ‘this’ argument discards qualifiers 
[-fpermissive]
  <stdin>:3:8: note:   in call to ‘void A::f(int)’

Better would be to explain why neither candidate is a match:

  <stdin>:8:6: error: no matching function for call to ‘A::f(int) const’
  <stdin>:2:8: note: candidate: ‘void A::f(int, int) const’
  <stdin>:2:8: note:   candidate expects 2 arguments, 1 provided
  <stdin>:3:8: note: candidate: ‘void A::f(int)’
  <stdin>:3:8: note:   passing ‘const A*’ as ‘this’ argument discards qualifiers


Same for

  void f(int, int);
  void f(int*);
  
  int main() {
    f(42);
  }

for which we currently emit

  <stdin>: In function ‘int main()’:
  <stdin>:5:5: error: invalid conversion from ‘int’ to ‘int*’ [-fpermissive]
  <stdin>:2:8: note:   initializing argument 1 of ‘void f(int*)’

instead of

  <stdin>: In function ‘int main()’:
  <stdin>:5:4: error: no matching function for call to ‘f(int)’
  <stdin>:1:6: note: candidate: ‘void f(int, int)’
  <stdin>:1:6: note:   candidate expects 2 arguments, 1 provided
  <stdin>:2:6: note: candidate: ‘void f(int*)’
  <stdin>:2:6: note:   conversion of argument 1 would be ill-formed:
  <stdin>:5:5: error: invalid conversion from ‘int’ to ‘int*’ [-fpermissive]

Reply via email to