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

2025-04-13


2844. Enumerating a finite set of built-in candidates

Section: 12.2.2.3  [over.match.oper]     Status: open     Submitter: Brian Bi     Date: 2023-07-14

(From submission #362.)

Consider the following example, which is accepted by clang, but rejected by gcc:

 #include <concepts> struct S1 { operator int*() { return nullptr; } }; struct S2 { template <class T> operator T() requires std::same_as<T, int*> { return nullptr; } }; int main() { S1 s1; S2 s2; return s1 - s2; } 

The question is whether the implementation is required to find the built-in candidate std::ptrdiff_t operator-(int*, int*), and select that candidate. Subclause 12.5 [over.built] specifies an infinite number of built-in candidates, such as std::ptrdiff_t operator-(T*, T*) for every object type T. If there are infinitely many built-in candidates, the implementation cannot iterate through all of them to determine whether each candidate is viable.

The rule in 12.2.2.3 [over.match.oper] paragraph 3.3 is insufficient:

Suggested resolution:

  1. Add a new paragraph before 12.5 [over.built] paragraph 3 as follows:

    For the purposes of this subclause, a type T is admissible for an operand E if a standard conversion sequence (12.2.4.2.2 [over.ics.scs]) exists from E to T. If E has a class type, then T is also admissible for E if E's class has a non-template conversion function F (11.4.8.3 [class.conv.fct]) that would be viable (12.2.3 [over.match.viable]) for a call of the form (E).N(), where N is a hypothetical id-expression that names F, and a standard conversion sequence to T exists from the type specified by F. If E denotes an overload set (12.3 [over.over]), then T is admissible for E if E contains any non-template function for which T is admissible.

    In the remainder of this subclause, vq represents either volatile or no cv-qualifier.

  2. Change in 12.5 [over.built] paragraph 4 through 6 as follows:

    For every pair (T , vq), where T is a cv-unqualified arithmetic type other than bool or a cv-unqualified pointer to (possibly cv-qualified) object type, there exist candidate operator functions of the form

     vq T& operator++(vq T&); T operator++(vq T&, int); vq T& operator--(vq T&); T operator--(vq T&, int); 
    if vqT& is admissible for the operand.

    For every (possibly cv-qualified) object type T and for every function type T that has neither cv-qualifiers nor a ref-qualifier, there exist candidate operator functions of the form

    T& operator*(T *);
    if T* is admissible for the operand.

    For every type T such that T* is admissible for the operand, there exist candidate operator functions of the form

     T* operator+(T *); 

  3. Change in 12.5 [over.built] paragraph 9 as follows:

    For every quintuple (C1 , C2 , T , cv1, cv2 ), where C2 is a class type, C1 is the same type as C2 or is a derived class of C2 , and T is an object type or a function type, there exist candidate operator functions of the form
     cv12 T & operator->*(cv1 C1 *, cv2 T C2 ::*); 
    where cv12 is the union of cv1 and cv2, if cv2TC2::* is admissible for the second operand. The return type is shown for exposition only; see 7.6.4 [expr.mptr.oper] for the determination of the operator's result type.
  4. Change in 12.5 [over.built] paragraph 13 through 16 as follows:

    For every cv-qualified or cv-unqualified object type T there exist candidate operator functions of the form

     T* operator+(T *, std::ptrdiff_t); T& operator[](T *, std::ptrdiff_t); T* operator-(T *, std::ptrdiff_t); 
    if T* is admissible for the first operand.

    For every cv-qualified or cv-unqualified object type T there exist candidate operator functions of the form

     T* operator+(std::ptrdiff_t, T *); T& operator[](std::ptrdiff_t, T *); 
    if T* is admissible for the second operand.

    For every T , where T is a pointer to object type and is admissible for the left or right operand, there exist candidate operator functions of the form

     std::ptrdiff_t operator-(T , T ); 

    For every T, where T is an enumeration type, or a pointer type that is admissible for the left or right operand, there exist candidate operator functions of the form

     bool operator==(T , T ); bool operator!=(T , T ); bool operator<(T , T); bool operator>(T , T ); bool operator<=(T , T ); bool operator>=(T , T ); R operator<=>(T , T ); 
    where R is the result type specified in 7.6.8 [expr.spaceship].

    For every T, where T is a pointer-to-member type and is admissible for the left or right operand, or T isstd::nullptr_t, there exist candidate operator functions of the form

     bool operator==(T, T ); bool operator!=(T , T ); 

  5. Change in 12.5 [over.built] paragraph 19 through 21 as follows:

    For every pair (T , vq), where T is any type, there exist candidate operator functions of the form

     T *vq & operator=(T *vq &, T *); 
    if Tvq& is admissible for the left operand or T* is admissible for the right operand.

    For every pair (T , vq), where T is an enumeration type, or T is a pointer-to-member type such that vqT& is admissible for the left operand or T is admissible for the right operand, there exist candidate operator functions of the form

     vq T & operator=(vq T &, T ); 

    For every pair (T , vq), where T is a cv-qualified or cv-unqualified object type, there exist candidate operator functions of the form

     T *vq & operator+=(T *vq &, std::ptrdiff_t); T *vq & operator-=(T *vq &, std::ptrdiff_t); 
    if T*vq& is admissible for the left operand.

  6. Change in 12.5 [over.built] paragraph 25 as follows:

    For every type T, where T is a pointer, pointer-to-member, or scoped enumeration type,
    • T is admissible for the second or third operand and is a pointer or pointer-to-member type, or
    • T is a scoped enumeration type,
    there exist candidate operator functions of the form
     operator?:(bool, T, T); 

Additional notes (March, 2025)

The following cases where infinitely many candidates exist are not addressed:

 struct A { operator int(); }; +A(); // if an implementation supports an unbounded set of extended integer types

(Also, the wording above does not change 12.5 [over.built] paragraph 10.)

 struct A { operator nullptr_t(); }; A() + 0; // nullptr_t has a standard conversion sequence to every pointer type
 struct A {}; template<typename T> struct B : A {}; using U = int A::*; struct C { operator U(); }; C() == C(); // unbounded set of possible derived classes, when converting to int B<T>::* for any T 


close