This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115e. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-11-18


2815. Overload resolution for references/pointers to noexcept functions

Section: 12.2.4.3  [over.ics.rank]     Status: ready     Submitter: Brian Bi     Date: 2023-10-05

Consider:

  void f() noexcept {}

  void g(void (*)() noexcept) {}
  void g(void (&)()) {}

  int main() {
    g(f);     // error: ambiguous
  }

In contrast:

  void f() noexcept {}

  void g(void (*)()) {} 
  void g(void (&)()) {}      // #1

  int main() {
    g(f);    // OK, calls #1
  }

In both cases, binding void(&)() to void() noexcept is considered an identity conversion, without further disambiguation by 12.2.4.3 [over.ics.rank].

CWG 2024-06-26

Binding a reference to a function should not be considered an identity conversion if it strips a non-throwing exception specification. This amendment removes the ambiguity for the first example and makes the second example ambiguous, which is desirable.

Proposed resolution (approved by CWG 2024-10-11):

  1. Change in 12.2.4.2.5 [over.ics.ref] paragraph 1 as follows:

    When a parameter of type “reference to cvT” binds directly (9.4.4 [dcl.init.ref]) to an argument expression:
    • If the argument expression has a type that is a derived class of the parameter type, the implicit conversion sequence is a derived-to-base conversion (12.2.4.2 [over.best.ics]).
    • Otherwise, if T is a function type, or if the type of the argument is possibly cv-qualified T, or if T is an array type of unknown bound with element type U and the argument has an array type of known bound whose element type is possibly cv-qualified U, the implicit conversion sequence is the identity conversion. [Note 1: When T is a function type, the type of the argument can differ only by the presence of noexcept. —end note]
    • Otherwise, if T is a function type, the implicit conversion sequence is a function pointer conversion.
    • Otherwise, the implicit conversion sequence is a qualification conversion.
    [Example 1:
      struct A {};
      struct B : public A {} b;
      int f(A&);
      int f(B&);
      int i = f(b);    // calls f(B&), an exact match, rather than f(A&), a conversion
    
       void g() noexcept;
       int h(void (&)() noexcept); // #1
       int h(void (&)());          // #2
       int j = h(g);               // calls #1, an exact match, rather than #2, a function pointer conversion
    
    end example]
  2. Change in 12.2.4.3 [over.ics.rank] bullet 3.2.6 as follows:

      int f(const int &);
      int f(int &);
      int g(const int &);
      int g(int);
      int i;
      int j = f(i);   // calls f(int &)
      int k = g(i);   // ambiguous
    
      struct X {
        void f() const;
        void f();
      };
      void g(const X& a, X b) {
        a.f();     // calls X::f() const
        b.f();     // calls X::f()
      }
    
      int h1(int (&)[]);
      int h1(int (&)[1]);
      int h2(void (&)());
      int h2(void (&)() noexcept);
      void g2() {
        int a[1];
        h1(a);
        extern void f2() noexcept;
        h2(f2);
      }