Function Overloading

Definition

In Cangjie, function overloading refers to the situation where a function name corresponds to multiple function definitions in a scope.

  • Overloaded functions have the same name but differ in the number or type of their parameters. The following is an example:

    // Scenario 1
    func f(a: Int64): Unit {
    }
    
    func f(a: Float64): Unit {
    }
    
    func f(a: Int64, b: Float64): Unit {
    }
    
  • When two generic functions have the same name, all the generic parameters of one of themn are renamed to be the same as the other. If the parameter types of the resulting functions are different (including the renamed generic types as well as any non-generic types), then they are overloaded functions. Otherwise, a duplicate definition error is reported (constraints of type variables are not taken into account). The following is an example:

    interface I1{}
    interface I2{}
    
    func f1<X, Y>(a: X, b: Y) {}
    func f1<Y, X>(a: X, b: Y) {} // Ok: after renaming generic type parameters, it will be 'func f1<X, Y>(a: Y, b: X)'
    
    func f2<T>(a: T) where T <: I1 {}
    func f2<T>(a: T) where T <: I2 {} // Error, not overloading
    
  • If two constructors in the same class have different parameters, they are overloaded constructors. The following is an example:

    // Scenario 2
    class C {
        var a: Int64
        var b: Float64
    
        public init(a: Int64, b: Float64) {
            this.a = a
            this.b = b
        }
    
        public init(a: Int64) {
            b = 0.0
            this.a = a
        }
    }
    
  • If a primary constructor and the init constructor in the same class have different parameters, they are overloaded constructors (the primary constructor and the init constructor are considered to have the same name). The following is an example:

    // Scenario 3
    class C {
        C(var a!: Int64, var b!: Float64) {
            this.a = a
            this.b = b
        }
    
        public init(a: Int64) {
            b = 0.0
            this.a = a
        }
    }
    
  • Two functions defined in different scopes can be overloaded in a scope that is visible to both of them. The following is an example:

    // Scenario 4
    func f(a: Int64): Unit {
    }
    
    func g() {
        func f(a: Float64): Unit {
        }
    }
    
  • Two functions defined in a parent class and a child class respectively can be overloaded in a scope that is visible to both of them. The following is an example:

    // Scenario 5
    open class Base {
        public func f(a: Int64): Unit {
        }
    }
    
    class Sub <: Base {
        public func f(a: Float64): Unit {
        }
    }
    

Only function declarations are allowed to introduce overloading, with the following exceptions:

  • In a class, interface, enum or struct type, a member function cannot have both static and non-static overloads.
  • In an enum type, a constructor and a member function (static or non-static) cannot be overloaded.

Variable declarations cannot introduce function overloading, even if they are of function type, so declaring multiple variables with the same name in the same scope is not allowed. In the following example, two variables with the same name are functions with different parameter types, but they are not introduced by function declarations. Therefore, they cannot be overloaded and a compilation error (redefinition error) is reported.

main() {
    var f: (Int64) -> Unit
    var f: (Float64) -> Unit
}

In the following example, although the variable f is of function type, a compilation error (redefinition error) is reported because a variable and a function cannot have the same name:

main() {
    var f: (Int64) -> Unit

    func f(a: Float64): Unit {   // Error, functions and variables cannot have the same name.
    }
}

In the following example, although the f static member function and the f instance member function have different parameter types, a compilation error is reported because a static member function and an instance member function in the same class cannot be overloaded:

class C {
    public static func f(a: Int64): Unit {
    }
    public func f(a: Float64): Unit {
    }
}

Function Overloading Resolution

When encountering a function call, all functions that can be called (functions that are visible in the current scope and can pass the type check) form a candidate set. As there may be multiple functions in the candidate set, function overloading resolution is required to select a function from it. The rules are as follows:

  • A function in a higher-level scope is preferred. In functions with nested expressions or functions, the inner scopes are of higher level.

    In the following example, when g(Sub()) is called in the function body of inner, the candidate set includes g defined in inner and g defined outside inner. The definition of g in inner is selected during function resolution because it is in a higher-level scope.

    open class Base {}
    class Sub <: Base {}
    
    func outer() {
        func g(a: Sub) {
            print("1")
        }
    
        func inner() {
            func g(a: Base) {
                print("2")
            }
    
            g(Sub())   // Output: 2
        }
    }
    
  • If there are still multiple functions in a higher-level scope, the most specific function is selected. Assume that there are functions f and g and given arguments. If g can be called when f is called but the reverse is not true, f is a more specific function than g. If there is no unique function that is most specific, an error is reported.

    In the following example, the two functions both named g are defined in the same scope, and the more specific function g(a: Sub): Unit is selected:

    open class Base {}
    class Sub <: Base {}
    
    func outer() {
        func g(a: Sub) {
            print("1")
        }
        func g(a: Base) {
            print("2")
        }
    
        g(Sub())   // Output: 1
    
    }
    
  • Child classes and parent classes are considered to be in the same scope. In the following example, a function g is defined in the parent class, and another function g is defined in the child class. When s.g(Sub()) is called, the two functions g are considered to be in the same scope during resolution, and the better matched function g(a: Sub): Unit defined in the parent class is selected.

    open class Base {
        public func g(a: Sub) { print("1") }
    }
    
    class Sub <: Base {
        public func g(a: Base) {
            print("2")
        }
    }
    
    func outer() {
        let s: Sub = Sub()
        s.g(Sub())   // Output: 1
    }