コンピュータプログラミングにおいて、演算子オーバーロード(演算子アドホック多態性とも呼ばれる)は、多態性の一種であり、異なる演算子が引数に応じて異なる実装を持つというものです。演算子オーバーロードは一般的に、プログラミング言語、プログラマ、またはその両方によって定義されます。
演算子オーバーロードは構文糖衣の一種であり、対象ドメインに近い表記法でプログラミングを可能にし[ 1 ]、ユーザー定義型が言語に組み込まれた型と同様の構文サポートを受けられるようにするため使用されます。例えば、科学計算では、数学的オブジェクトの計算表現を紙面と同じ構文で操作できるため、よく用いられます。
演算子オーバーロードは、関数呼び出しでエミュレートできるため、(関数を含む)言語の表現力を変えるものではありません。たとえば、行列などのユーザー定義型の変数a、bおよびを考えてみましょう。c
a + b * c
演算子オーバーロードをサポートする言語において、演算子が演算子よりも優先順位*が高いという通常の前提の下では、これは簡潔な記述方法です。+
Add(a, Multiply(b, c))
しかし、前者の構文は一般的な数学的用法を反映している。
この場合、 C++Timeではユーザー定義型での加算を可能にするために加算演算子がオーバーロードされています。
Time operator + ( const Time & lhs , const Time & rhs ) { Time temp = lhs ; temp . seconds += rhs . seconds ; temp . minutes += temp . seconds / 60 ; temp . seconds %= 60 ; temp . minutes += rhs . minutes ; temp . hours += temp . minutes / 60 ; temp . minutes %= 60 ; temp . hours += rhs . hours ; return temp ; }加算は二項演算であり、2つのオペランドを持ちます。C++では、渡される引数がオペランドであり、tempオブジェクトが戻り値です。
この操作は、クラスメソッドとして定義することもできますが、その場合は、lhs隠された引数に置き換えますthis。ただし、これにより、左オペランドは型に強制されますTime。
// 開き括弧の直前の "const" は、`this` が変更されないことを意味します。Time Time :: operator + ( const Time & rhs ) const { Time temp = * this ; // `this` は変更されないので、コピーを作成します。temp . seconds += rhs . seconds ; temp . minutes += temp . seconds / 60 ; temp . seconds %= 60 ; temp . minutes += rhs . minutes ; temp . hours += temp . minutes / 60 ; temp . minutes %= 60 ; temp . hours += rhs . hours ; return temp ; }クラスメソッドとして定義された単項演算子は、明らかな引数を受け取らないことに注意してください( からのみ機能しますthis)。
bool Time :: operator ! () const { return hours == 0 && minutes == 0 && seconds == 0 ; }小なり演算子(<)は、構造体やクラスをソートするためにオーバーロードされることがよくあります。
class IntegerPair { private : int x ; int y ; public : explicit IntegerPair ( int x = 0 , int y = 0 ) : x { x }, y { y } {}bool operator < ( const IntegerPair & p ) const { if ( x == p . x ) { return y < p . y ; } return x < p . x ; } };前の例と同様に、最後の例でも演算子オーバーロードはクラス内で行われます。C++では、小なり演算子(operator<)をオーバーロードした後、標準のソート関数を使用して一部のクラスをソートできます。
C++20で三方向比較演算子 ( )が導入されて以来operator<=>、すべての順序演算子は、その演算子を定義するだけで定義できるようになりました。三方向比較演算子は、C++、Python、Rust、Swift、PHPなど、多くの言語に存在します。JavaやC#などの他の言語では、代わりにメソッド を使用します。Comparable.compareTo()
import std ;std :: strong_orderingを使用します。class IntegerPair { private : int x ; int y ; public : explicit IntegerPair ( int x = 0 , int y = 0 ) : x { x }, y { y } {}// = default で自動生成できます。strong_ordering operator < ( const IntegerPair & p ) const { if ( strong_ordering cmp = x <=> p . x ; cmp != strong_ordering :: equal ) { return cmp ; } return y <=> p . y ; } };演算子オーバーロードは、演算子のセマンティクスをオペランドの型に応じてプログラマーが再割り当てできるため、批判されてきました[ 2 ] 。たとえば、 C++<<の演算子を使用すると、とが整数型の場合、変数のビットがビットだけ左にシフトされますが、が出力ストリームの場合、上記のコードはストリームにを書き込もうとします。演算子オーバーロードは、元のプログラマーが演算子の通常のセマンティクスを変更し、後続のプログラマーを驚かせる可能性があるため、演算子オーバーロードは慎重に使用するのが良しとされています(Javaの作成者はこの機能を使用しないことを決定しましたが、必ずしもこの理由からではありません[ 3 ])。a<<bababab
演算子に関するもう 1 つの、より微妙な問題は、数学の特定の規則が誤って期待されたり、意図せず仮定されたりする可能性があることです。たとえば、 + の可換性(つまりa + b == b + a) は常に適用されるとは限りません。オペランドが文字列の場合、この例が発生します。これは、+ が文字列の連結を実行するためにオーバーロードされることが多いためです (つまり、 は"bird" + "song"を生成し"birdsong"、 は"song" + "bird"を生成します"songbird")。この議論に対する典型的な反論は、数学から直接得られます。+ は整数 (より一般的には任意の複素数) に対して可換ですが、他の「型」の変数に対しては可換ではありません。実際には、丸め誤差のために、たとえば浮動小数点値の場合、+ は常に結合法則を満たすとは限りません。別の例: 数学では、乗算は実数と複素数に対しては可換ですが、行列の乗算では可換ではありません。
いくつかの一般的なプログラミング言語は、演算子がプログラマによってオーバーロード可能かどうか、および演算子が事前に定義されたセットに限定されているかどうかに基づいて分類されます。
ALGOL 68仕様では、演算子のオーバーロードが許可されていた。[ 36 ]
ALGOL 68言語仕様書(177ページ)から抜粋した、オーバーロードされた演算子¬、=、≠、およびabsの定義箇所:
10.2.2. ブール演算子に対する演算 a) op ∨ = ( bool a, b) bool :( a | true | b ); b) op ∧ = ( bool a, b) bool : ( a | b | false ); c) op ¬ = ( bool a) bool : ( a | false | true ); d) op = = ( bool a, b) bool :( a∧b ) ∨ ( ¬b∧¬a ); e) op ≠ = ( bool a, b) bool : ¬(a=b); f) op abs = ( bool a) int : ( a | 1 | 0 );
演算子をオーバーロードするために特別な宣言は必要なく、プログラマは自由に新しい演算子を作成できます。二項演算子については、他の演算子に対する優先順位を設定できます。
優先度最大値= 9; op max = ( int a, b) int : ( a>b | a | b ); op ++ = ( ref int a ) int : ( a +:= 1 );
Adaは、Ada 83言語規格の公開当初から演算子のオーバーロードをサポートしています。しかし、言語設計者は新しい演算子の定義を禁止しました。オーバーロードできるのは、既存の演算子のみで、例えば「+」、「*」、「&」などの識別子を持つ新しい関数を定義することで実現されます。1995年と2005年の言語改訂版でも、既存の演算子のオーバーロードに限定されています。
Sun MicrosystemsのJava言語設計者は、オーバーロードを省略することを選択した。[ 38 ] [ 39 ] [ 40 ]演算子オーバーロードについて尋ねられたとき、Oracleの Brian Goetzは「まず値型、それからそれについて話せます」と答え、Project Valhallaの後にオーバーロードが追加される可能性があることを示唆した。[ 41 ]
Python では、特別な名前のメソッドを実装することで演算子オーバーロードが可能です。[ 42 ]例えば、加算 (+) 演算子は、メソッドを実装することでオーバーロードできますobj.__add__(self, other)。
Rubyでは、単純なメソッド呼び出しのための構文糖衣として、演算子オーバーロードが利用できます。
Luaでは、メソッド呼び出しの構文糖衣として演算子オーバーロードが使用できます。さらに、最初のオペランドがその演算子を定義していない場合は、2番目のオペランドのメソッドが使用されるという機能が追加されています。
Microsoft は2001 年にC#に、2003 年にVisual Basic .NETに演算子オーバーロードを追加しました。C # の演算子オーバーロードは、構文が C++ の演算子オーバーロードと非常によく似ています。[ 43 ]
public class Fraction { private int numerator ; private int denominator ;// ...public static Fraction operator + ( Fraction lhs , Fraction rhs ) => new Fraction ( lhs . numerator * rhs . denominator + rhs . numerator * lhs . denominator , lhs . denominator * rhs . denominator ); }Scalaはすべての演算子をメソッドとして扱うため、代理による演算子オーバーロードが可能です。
In Raku, the definition of all operators is delegated to lexical functions, and so, using function definitions, operators can be overloaded or new operators added. For example, the function defined in the Rakudo source for incrementing a Date object with "+" is:
multiinfix:<+>(Date:D$d, Int:D$x) { Date.new-from-daycount($d.daycount + $x) } Since "multi" was used, the function gets added to the list of multidispatch candidates, and "+" is only overloaded for the case where the type constraints in the function signature are met. While the capacity for overloading includes +, *, >=, the postfix and term i, and so on, it also allows for overloading various brace operators: [x, y], x[y], x{y}, and x(y).
Kotlin has supported operator overloading since its creation by overwriting specially named functions (like plus(), inc(), rangeTo(), etc.)[44]
dataclassPoint(valx:Int,valy:Int){operatorfunplus(other:Point):Point{returnPoint(this.x+other.x,this.y+other.y)}}Because both Kotlin and Java compile to .class, when converted back to Java this will just be represented as:
publicclassPoint{// fields and constructor...publicPointplus(Pointother){returnnewPoint(this.x+other.x,this.y+other.y);}}Operator overloading in Rust is accomplished by implementing the traits in std::ops.[45]
usestd::ops::Add;#[derive(Debug)] struct Point { x : i32 , y : i32 }impl Point { pub fn new ( x : i32 , y : i32 ) -> Self { Point { x , y } } }impl Add for Point { type Output = Point ;fn add ( self , other : Point ) - > Point { Point { x : self.x + other.y , y : self.y + other.y } } }fn main () { let p1 : Point = Point :: new ( 1 , 2 ); let p2 : Point = Point :: new ( 3 , 4 ); let sum : Point = p1 + p2 ; println! ( "p1とp2の合計: {:?}" , sum ); }One of the nicest features of C++ OOP is that you can overload operators to handle objects of your classes (you can't do this in some other OOP-centric languages, like Java).