何でできなんだ…と疑問に思ったのでちょっと調べてみました。
演算子を再定義出来る言語は意外と少ないです。C++、C#、Scala、Kotlin、Python、Ruby、Perl6、Haskell、Swift、Rust、あれ、以外とあるっぽい。ただ、これらの言語を大きく分けると、演算子が関数としてオーバーロードしている言語と、演算子がただのインスタンスメソッドにすぎない言語に大別されます。
a + b
という表現を解釈するとき、+(a, b)
となるかa.+(b)
となるかはかなりの違いがあります。C++はその柔軟性を生かすために、両方の解釈が出来るようにしました。**これが混乱の元です。**オブジェクト指向としてはa.+(b)
とインスタンスメソッドとして解釈する方が正しく思えます。しかし、+(a, b)
と解釈できる仕様は、オープンクラスではないC++のような言語において、既存の型を前置、ユーザー定義型を後置に取る演算子の動作を追加するために必要です。この+(a, b)
解釈が出来ない言語では、例えばScalaは暗黙の型変換という仕組み、Rubyはcoerceというメソッドを介するという仕組み、といった別手段を用いないといけません。そう、a.+(b)
という解釈だけでは無理が生じるので、C++では+(a, b)
の両方が出来るようになったのです。
C#はC++からの簡素化を目指しました。演算子オーバーロードについても簡素化し、C++のような二つも書き方があるようなものは避けたかったのです。で、どうしたかというと、+(a, b)
を取りました。この書き方は、インスタンスメソッドではありませんのでインスタンスに紐付けらる物ではありません。つまり、C++のように関数として書くか、C#のようにクラスメソッド(staticメソッド)として書く必要があると言うことです。
後は簡単です。インターフェースはインスタンスの動きの枠組みを決めるためのものであって、クラスそのものの枠組みを決めるための物ではありません。だからstaticメソッドは対象外になる、ただそれだけです。
なお、単なるメソッドであるScalaやRubyでは演算子の再定義は(必要であれば)積極的に使われているようにみえます。これらの言語は、書き方の違いだけで、他のメソッドと違いは無いからだと思います。
【おまけ】
Stack Overflowに何が何でも強制させたいという手法が載っていました。ただし、抽象クラスに限ります。
interface - Is there any way in C# to enforce operator overloading in derived classes? - Stack Overflow
C#
1public abstract class MyClass
2{
3 public static MyClass operator +(MyClass c1, MyClass c2)
4 {
5 return c1.__DoAddition(c2);
6 }
7
8 protected abstract MyClass __DoAddition(MyClass c2);
9}