利用者定義演算子

利用者定義演算子 (りようしゃていぎえんざんし ) とはプログラミング言語において、言語の利用者が演算子に対し組み込みの演算子とは異なる動作を定義できる機能である。
概要.
古くはFORTRANから導入された機能である。当初は可読性と記述性の観点から各種言語に取り入れられた機能であるが、SmalltalkとC++によるオブジェクト指向の発達とともに多態性を実現するための機能としての側面も持つようになった。利用者定義演算子の定義はどのような言語でも関数またはメンバー関数あるいはメソッドのいずれかで定義するようになっている。
演算子定義の例.
Smalltalkによる例:
Object
 subclass: #Value
 instanceVariableNames: 'value'
 classVariableNames: "
 poolDictionaries: "
 category: 'Example'.
Value
 createGetMethod: 'value' default: 0;
 createSetMethod: 'value'.
Value methodsFor: 'accessing'
species
 " 演算子の戻り値型としてValue自身を使う。 "
 ^ Value.
" '+' 演算子と '-' 演算子の定義。 "
Value methodsFor: 'operator'
+ aNumber
 ^ self species with: self value + aNumber.
- aNumber
 ^ self species with: self value - aNumber.
Value methodsFor: 'instance creation'
with: aNumber
 ^ super new value: aNumber.
value1 := Value with: 10.
value2 := Value with: 5.
" 利用者定義演算子を使用したため各変数には整数オブジェクトではなくValueクラスのオブジェクトが代入されている。 "
value0 := value1 + value2.
value0 := value1 - value2.
Smalltalkにおいてはメソッドに記号だけで構成されるセレクターをつけることで利用者定義演算子を定義することができる。Smalltalkでは演算子を2項セレクターとよびcodice_1など英数でできたセレクターとほぼ同様に扱う。Smalltalkにおいて演算子はメッセージの一種という扱いであり引数が必ず1個で優先順位が異なる点以外は特別扱いはしない。このため演算子として定義できる記号には殆ど制限がない(ただし区切り用の記号や代入記号は指定できない)。また、演算子として定義する記号は2文字でもよくcodice_2やcodice_3といった演算子がよく定義されている。Smalltalkは多重定義ができないため一つのクラスに同じ演算子を複数定義することはできない。ただし、インスタンスオブジェクトとクラスオブジェクトは同じクラスに紐づくものの別のオブジェクトであるためインスタンスメソッドとクラスメソッドで同じ演算子を定義することが可能になっている。
C++による例:
class Value
 int value;
public:
 Value( int value ):
 value( value )
 // '+' 演算子の定義。
 Value operator + ( Value const &source ) const
 return Value( source.value + value );
 // '-' 演算子の定義。
 Value operator - ( Value const &source ) const
 return Value( source.value - value );
 // 単項演算子版の'-'演算子の定義。
 Value operator - (void) const
 return Value( -value );
};
// 大域関数版演算子の定義。
Value operator + ( int left, Value const &right )
 return Value( left ) + right;
Value operator - ( int left, Value const &right )
 return Value( left ) - right;
int main(void)
 Value
 value0( 0 ),
 value1( 10 ),
 value2( 5 );
 // 利用者定義演算子を使用しているためどの変数も数値型ではなくValue型になっている。
 value0 = value1 + value2;
 value0 = value1 - value2;
 // メンバー関数では1項目はValue型でなければならないが大域関数を定義しているため1項目に数値を指定できる。
 value0 = 10 + value2;
 value0 = 10 - value2;
 return EXIT_SUCCESS;
C++においてはcodice_4キーワードの後に記号をつけた形の特殊な名前を持つ関数を定義することで演算子を定義できる。C++では言語機能として用意されている演算子しか定義できず独自の記号を用いた演算子を定義することはできない。また、演算子の引数や戻り値の型は演算子の種類によって制限される。例えば型のメンバーを指定するcodice_2演算子を単項演算子として独自に定義したり、数値型やcodice_2演算子を定義していない型を戻り値の型として指定することはできない。多くの制限があるなか下記のような通常の関数と異なる演算子独自の振る舞いをする演算子があり通常の関数では不可能な構文を記述することができるようになっている。Smalltalkでは2項演算子しか定義することはできないがcodice_7 codice_8といった単項演算子は勿論codice_9といった添字演算子やcodice_10といった関数呼出演算子なども定義することができる。
C++は多重定義が可能な言語であり、利用者定義演算子は多重定義の枠組みに入っている。このためSmalltalkと異なり、引数が異なる場合に限って同じ名前空間で同じ名前の演算子を複数定義することが可能となっている。ただし、既存の演算子を上書きすることになってしまうため数値型だけを引数とする演算子の定義はできない。
オブジェクト指向と利用者定義演算子.
いくつかのオブジェクト指向言語ではFORTRANと同じ可読性と記述性の観点で導入されているが、多くのオブジェクト指向言語においては数値型とオブジェクトを同一の関数(あるいはメソッド)で処理する観点で導入されておりそれらの言語では必須の機能となっている。
純粋なオブジェクト指向言語(特にSmalltalk)においてオブジェクトに対する操作は全てメソッドで受信可能なメッセージとして表されるべきであり、セレクターが記号になっているだけのメッセージである演算子は特別扱いすべきではない。このため優先度の違いはあるものの演算子は単なるセレクターの一種として位置づけられている。
利用者定義演算子による多態性の例.
Smalltalkによる例:
"中央位置を返すブロックオブジェクト"
center :=
[ :edge0 :edge1 |
 ( edge0 + edge1 ) / 2.
"スカラー値同士の中央位置を算出"
result :=
 center
 value: 10
 value: 5.
"座標同士の中央位置を算出"
result :=
 center
 value: 10 @ 10
 value: 5 @ 5.
C++による例:
// 中央位置を返す関数
template< class Type > auto Center( Type const &edge0, Type const &edge1 )
 return ( edge0 + edge1 ) / 2; // 除算演算子の実体はCenterの引数によって変わる
int main(void)
 int value;
 // スカラー値同士の中央位置を算出
 int result0 = Center( 10, 5 );
 // アドレス同士の中央位置を算出
 off_t result1 = Center( &value + 10, &value + 5 );
 // 複素数同士の中央値を算出
 std::complex< double > result2 = Center( std::complex< double >( 10, 10 ), std::complex< double >( 5, 5 ) );
 return EXIT_SUCCESS;