利用者定義演算子

利用者定義演算子 (りようしゃていぎえんざんし ) とはプログラミング言語において、言語の利用者が演算子に対し組み込みの演算子とは異なる動作を定義できる機能である。

概要 
古くは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.
!!

| value0 value1 value2 |

value1 := Value with: 10.
value2 := Value with: 5.

" 利用者定義演算子を使用したため各変数には整数オブジェクトではなくValueクラスのオブジェクトが代入されている。 "
value0 := value1 + value2.
value0 := value1 - value2.

Smalltalkにおいてはメソッドに記号だけで構成されるセレクターをつけることで利用者定義演算子を定義することができる。Smalltalkでは演算子を2項セレクターとよび#with:など英数でできたセレクターとほぼ同様に扱う。Smalltalkにおいて演算子はメッセージの一種という扱いであり引数が必ず1個で優先順位が異なる点以外は特別扱いはしない。このため演算子として定義できる記号には殆ど制限がない(ただし区切り用の記号や代入記号は指定できない)。また、演算子として定義する記号は2文字でもよく->や~=といった演算子がよく定義されている。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++においてはoperatorキーワードの後に記号をつけた形の特殊な名前を持つ関数を定義することで演算子を定義できる。C++では言語機能として用意されている演算子しか定義できず独自の記号を用いた演算子を定義することはできない。また、演算子の引数や戻り値の型は演算子の種類によって制限される。例えば型のメンバーを指定する->演算子を単項演算子として独自に定義したり、数値型や->演算子を定義していない型を戻り値の型として指定することはできない。多くの制限があるなか下記のような通常の関数と異なる演算子独自の振る舞いをする演算子があり通常の関数では不可能な構文を記述することができるようになっている。Smalltalkでは2項演算子しか定義することはできないが+x -xといった単項演算子は勿論x[i]といった添字演算子やx( a, b, c )といった関数呼出演算子なども定義することができる。

C++は多重定義が可能な言語であり、利用者定義演算子は多重定義の枠組みに入っている。このためSmalltalkと異なり、引数が異なる場合に限って同じ名前空間で同じ名前の演算子を複数定義することが可能となっている。ただし、既存の演算子を上書きすることになってしまうため数値型だけを引数とする演算子の定義はできない。

使用可能な言語

表内の注記

オブジェクト指向と利用者定義演算子 
いくつかのオブジェクト指向言語ではFORTRANと同じ可読性と記述性の観点で導入されているが、多くのオブジェクト指向言語においては数値型とオブジェクトを同一の関数(あるいはメソッド)で処理する観点で導入されておりそれらの言語では必須の機能となっている。

純粋なオブジェクト指向言語(特にSmalltalk)においてオブジェクトに対する操作は全てメソッドで受信可能なメッセージとして表されるべきであり、セレクターが記号になっているだけのメッセージである演算子は特別扱いすべきではない。このため優先度の違いはあるものの演算子は単なるセレクターの一種として位置づけられている。

利用者定義演算子による多態性の例 
Smalltalkによる例:
| center result |

"中央位置を返すブロックオブジェクト"
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;
}

脚注

関連項目 
 多重定義

プログラミング言語の演算子
定義