Implicit Conversion Sequence (ICS)

Standard Conversion Sequence (SCS)

User-Defined Conversion Sequence

Ellipsis Conversion Sequence

An ellipsis conversion sequence occurs when an argument in a function call is matched with the ellipsis parameter specification of the function called. ⸺ [over.ics.ellipsis]/1

Ranking Implicit Conversion Sequence

Ranking Standard Conversion Sequence

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if, when you follow the rules in §13.3.3.2 from top to bottom—at each step asking “is S1 better than S2?”—you find a rule that favors S1 and stop there; if you exhaust all the rules without a decision, S1 and S2 remain indistinguishable:

  1. Removing top-level cv-qualifiers. (added by me)
  2. If parameter lists are the same, causing redefinition error. (added by me)
  3. S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that, (PH: this step serves as a short circuit)
     #include <iostream>
    	
     void f(int) { std::cout << "A" << std::endl; }
     void f(short) { std::cout << "B" << std::endl; }
    	
     int main() {
         int a = 1;
         f(a); // Calls f(int)
         return 0;
     }
    
     #include <iostream>
    	
     void f(int*) { std::cout << "A" << std::endl; }
     void f(const int*) { std::cout << "B" << std::endl; }
    	
     int main() {
         int a = 1;
         f(&a); // Calls f(int*)
         return 0;
     }
    
  4. The rank of S1 is better than the rank of S2, or, if not that,
    #include <iostream>
    	
    void f(int) { std::cout << "A" << std::endl; }
    void f(unsigned short) { std::cout << "B" << std::endl; }
    	
    int main() {
        short a = 1;
        f(a);
        return 0;
    }
    
  5. S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.
    #include <iostream>
        
    void foo(int*){ std::cout << "A\n"; };
    void foo(const int*){ std::cout << "B\n"; };
        
    int main() {
        
        int value = 1;
        
        foo(&value);
        
        return 0;
    }
    
    #include <iostream>
        
    void foo(int * *       volatile * volatile const * const){ std::cout << "A\n"; };
    void foo(int * * const volatile * volatile const * const){ std::cout << "B\n"; };
        
    int main() {
        
        int value = 1;
        
        int *                            r  = &value;
        int * * volatile                 s  = &r;
        int * * volatile *               t  = &s;
        int * * volatile * const * const u = &t;
        
        foo(u); // A, both foo() are overloaded
        
        return 0;
    }
    
    #include <iostream>
        
    void foo(int *          * volatile * volatile const * const){ std::cout << "A\n"; };
    void foo(int * volatile * volatile * volatile const * const){ std::cout << "B\n"; };
        
    int main() {
        
        int value = 1;
        
        int *                            r  = &value;
        int * * volatile                 s  = &r;
        int * * volatile *               t  = &s;
        int * * volatile * const * const u = &t;
        
        foo(u); // A, not because of overloading but u cannot be converted to
                // int * volatile * volatile * volatile const * const
        
        return 0;
    }
    
    #include <iostream>
        
    void foo(int * *       volatile * volatile const * const){ std::cout << "A\n"; };
    void foo(int * * const volatile *          const * const){ std::cout << "B\n"; };
        
    int main() {
        
        int value = 1;
        
        int *                            r  = &value;
        int * * volatile                 s  = &r;
        int * * volatile *               t  = &s;
        int * * volatile * const * const u = &t;
        
        foo(u); // ambiguous
        
        return 0;
    }
    
  6. S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.
     #include <iostream>
    	
     void f(const int&) { std::cout << "A" << std::endl; }
     void f(volatile int&) { std::cout << "B" << std::endl; }
    	
     int main() {
        int a = 1;
        f(a); // Error: ambiguous call
        return 0;
     }
    
     #include <iostream>
    	
     void f(const volatile int&) { std::cout << "A" << std::endl; }
     void f(volatile int&) { std::cout << "B" << std::endl; }
    	
     int main() {
        int a = 1;
        f(a); // Calls f(volatile int&)
        return 0;
     }
    

Ranking User-Defined Conversion Sequence

Flowchart

Ranking implicit conversion

Exact match

lvalue-to-rvalue conversion

Promotions

Conversions

Reference binding

Reference binding(4)

  1. When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base conversion. ⸺[over.ics.ref]/1
  2. If the parameter binds directly to the result of applying a conversion function to the argument expression, the implicit conversion sequence is a user-defined conversion sequence, with the second standard conversion sequence either an identity conversion or, if the conversion function returns an entity of a type that is a derived class of the parameter type, a derived-to-base conversion. ⸺[over.ics.ref]/1
  3. When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the referenced type according to 12.4.4.2. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the referenced type with the argument expression. Any difference in top-level cv-qualification is subsumed by the initialization itself and does not constitute a conversion. ⸺[over.ics.ref]/2
    #include<iostream>
       
    void print(int ){};
    void print(const int&){};
       
    int main(){
       short i = 1;
          // print(int) requires lvalue-to-rvalue conversion
          // and integral promotion (Promotion)
          // print(const int&) requires promotion too
          // specifically, compiler first convert the argument `short` to `int`,
          // then copy-initializes a temporary `int` with the result of conversion,
          // and finally binds the reference to the temporary
       print(i);    // Error: ambiguous
       return 0;
    }
    
    #include<iostream>
       
    class B {
    public:
       B(unsigned int ui):value(ui){}
    private:
       unsigned int value;
    };
       
    class A {
    public:
       A(int i):value(i){}
       
       // Conversion A => B
       operator B() {
          return B(static_cast<unsigned int>(value));
       }
       
    private:
       int value;
    };
       
    void foo(const B&){ std::cout << "A\n"; }
       
    int main(){
       
       A a(1);
       
       // foo(const B&): user-defined conversion
       // const is required as a temporary object with type B will be created.
       foo(a);
    }
    
     #include<iostream>
    	
     void print(int& a){ std::cout<< a << std::endl; };
    	
     int main(){
        double d = 3;
        print(d);
        return 0;
     }
    

    However, if the reference parameter is const, the conversion is allowed.

     #include<iostream>
    	
     // Step 1: Convert double to a Temporary int (lvalue-to-rvalue conversion)
     // Step 2: Bind const int& to the Temporary int
     // Step 3: Use a in print()
     void print(const int& a){ std::cout<< a << std::endl; };
    	
     int main(){
        double d = 3;
        print(d);
        return 0;
     }