利用者定義演算子
概要 > 演算子定義の例

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と異なり、引数が異なる場合に限って同じ名前空間で同じ名前の演算子を複数定義することが可能となっている。ただし、既存の演算子を上書きすることになってしまうため数値型だけを引数とする演算子の定義はできない。
| 演算子宣言 | 名称 | 振る舞い | 応用例 |
| --- | --- | --- | --- |
| operator Type() | 型変換（キャスト）演算子 | Type 型を返す。Type 型への暗黙的な変換が可能となる。呼び出しにはstatic_castなどのキャスト構文による明示的な型変換も使える。C++11以降では、暗黙的な型変換を抑止して、明示的な型変換を強制するexplicit修飾子を指定することもできる。  応用例のようにif文やwhile文の条件式などで用いるbool型変換演算子の定義として用いられるほか、アトミック変数のクラステンプレートの内部型への暗黙変換などにも用いられる。 | std::unique_ptr<std::string> pointer( new std::string( "hoge" ) ); // 以下では std::unique_ptr::operator bool() が呼ばれる。 if( pointer ) { /* pointer 内部で管理されるポインターが NULL でない場合 */ }  |
| Type operator->() | アロー演算子 | Type 型を返す。通例、ポインター型Type*を返し、Typeのメンバーを使用できるように実装することで、疑似的なポインター型を定義することができる。  応用例のようなスマートポインターや排他[要説明]などのためによく用いられる。 | std::shared_ptr< std::vector<int> > pointer( new std::vector<int>() ); // 以下では std::shared_ptr::pointer operator->() が呼ばれる。 pointer->push_back(0); // push_back() は std::vector のメンバーだが、std::shared_ptr のメンバーではない。   |