Operator Overloading

Operator overloading enables you to allow a type to support an operator that is not supported by this type by default.

To overload an operator for a type, you must define a member function named after the operator. In this way, when the operator is called with an instance of this type, the operator function is automatically called.

The definition of an operator function is similar to that of a normal function. The differences are as follows:

  • When defining an operator function, add the operator modifier before the func keyword.
  • The number of parameters of an operator function must meet the requirements of the corresponding operator. For details, see Operators in the appendix.
  • An operator function can be defined only in class, interface, struct, enum, and extend.
  • An operator function has the semantics of an instance member function. Therefore, the static modifier cannot be used.
  • An operator function cannot be a generic function.

Note that the inherent priority and associativity of an operator is not changed after it is overloaded. For details, see Operators in the appendix.

Definition and Usage of Operator Functions

You can define operator functions in either of the following ways:

  1. For types that can directly contain function definitions (including struct, enum, class, and interface), operator functions can be added in the type definition.
  2. Use extend to add operator functions to types so that operators can be overloaded for them. For types that cannot directly contain function definitions (types other than struct, class, enum, and interface) or whose implementation cannot be changed, such as struct, class, enum, and interface defined by third parties, only this method can be used. For details, see Extension Overview.

The conventions on parameter types for operator functions are as follows:

  1. For unary operators, operator functions have no parameter, and there is no requirement on the return value type.

  2. For binary operators, operator functions have only one parameter, and there is no requirement on the return value type.

    The following examples show how to define and use unary and binary operators.

    - is used to negate both member variables x and y in a Point instance, returning a new Point object. + is used to add up the values of the two member variables x and y respectively in two Point instances, returning a new Point object.

    open class Point {
        var x: Int64 = 0
        var y: Int64 = 0
        public init (a: Int64, b: Int64) {
            x = a
            y = b
        }
    
        public operator func -(): Point {
            Point(-x, -y)
        }
        public operator func +(right: Point): Point {
            Point(this.x + right.x, this.y + right.y)
        }
    }
    

    Then, the - unary operator and the + binary operator can be used directly for Point instances.

    main() {
        let p1 = Point(8, 24)
        let p2 = -p1      // p2 = Point(-8, -24)
        let p3 = p1 + p2  // p3 = Point(0, 0)
    }
    
  3. Index operators ([]) have two forms: let a = arr[i] (for obtaining a value) and arr[i] = a (for assigning a value). They can be distinguished based on whether they contain the special named parameter value. Index operator overloading does not require that both forms be used at a time. You can use either one as required.

    In the expression let a = arr[i, j, k...], the sequence i, j, k... is used as the parameters of the overloaded operator []. Named parameters are not allowed. The parameter types of this form of the index operator, as well as the return type, are unrestricted.

    class A {
        operator func [](arg1: Int64, arg2: String): Int64 {
            return 0
        }
    }
    
    func f() {
        let a = A()
        let b: Int64 = a[1, "2"]
        // b == 0
    }
    

    In arr[i, j, k...] = a, the sequence i, j, k... is used as the parameters of the overloaded operator [], and the expression a on the right of = is passed as a special named parameter. This named parameter can have any type, but its name must be value and it is not allowed to specify a default value. The return type of the assignment operator [] must be Unit.

    Note that the use of the named value parameter is only as a marker and one does not need to pass it as a named parameter when an index operator is used to assign a value.

    class A {
        operator func [](arg1: Int64, arg2: String, value!: Int64): Unit {
            return
        }
    }
    
    func f() {
        let a = A()
        a[1, "2"] = 0
    }
    

    Immutable types except enum cannot overload index assignment arr[i] = a. The compiler will report an error if the user provides an overload of index assignment in an immutable type other than enum.

  4. When the function call operator () is overloaded, the input parameters and return value can be of any type. The following is an example:

    open class A {
        public init() {}
    
        public operator func ()(): Unit {}
    }
    
    func test1() {
        let a = A() // Ok, A() calls the constructor of A.
        a() // Ok, a() is to call the operator () overloading function.
    }
    

    The overloaded () operator cannot be called using this or super. The following is an example:

    open class A {
        public init() {}
        public init(x: Int64) {
            this() // Ok, this() calls the constructor of A.
        }
    
        public operator func ()(): Unit {}
    
        public func foo() {
            this()  // Error, this() calls the constructor of A.
            super() // Error
        }
    }
    
    class B <: A {
        public init() {
            super() // Ok, super() calls the constructor of the super class.
        }
    
        public func goo() {
            super() // Error
        }
    }
    

    For enum types, the constructor form has priority when a constructor and an overloaded () operator function are candidates. The following is an example:

    enum E {
        Y | X | X(Int64)
    
        public operator func ()(p: Int64) {}
        public operator func ()(p: Float64) {}
    }
    
    main() {
        let e = X(1) // Ok, X(1) is to call the constructor X(Int64).
        X(1.0) // Ok, X(1.0) is to call the operator () overloading function.
        let e1 = X
        e1(1) // Ok, e1(1) is to call the operator () overloading function.
        Y(1) // Ok, Y(1) is to call the operator () overloading function.
    }
    

Operators That Can Be Overloaded

The following table lists all operators that can be overloaded (in descending order of priority):

OperatorDescription
()Function call
[]Indexing
!NOT
-Negative
**Power
*Multiply
/Divide
%Remainder
+Add
-Subtract
<<Bitwise left shift
>>Bitwise right shift
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal
==Equal
!=Not equal
&Bitwise AND
^Bitwise XOR
|Bitwise OR

Note:

  1. If a binary operator other than a relational operator (<, <=, >, >=, ==, or !=) is overloaded for a type, and the return type of the operator function is the same as the type of a left operand or its subtype, the type supports the corresponding compound assignment operator. If the return type of an operator function is different from the type of a left operand or its subtype, a type mismatch error is reported when the corresponding compound assignment operator is used.
  2. Cangjie does not support custom operators. That is, only operators in the preceding table can be overloaded.
  3. If a type T already supports an overloaded operator, a redefinition error is reported when a new operator function with the same signature is implemented for T through an extension. For example, a redefinition error is reported when you overload an arithmetic operator, a bitwise operator, a relational operator, or other operators that are already supported by the number type with the same signature for the number type, when you overload a relational operator with the same signature for the Rune type, or when you overload a logical operator, the equal-to operator, or the not-equal-to operator with the same signature for the Bool type.