利用者定義演算子

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索

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

概要[編集]

古くはFORTRANから導入された機能である。当初は可読性と記述性の観点から各種言語に取り入れられた機能であるが、SmalltalkC++によるオブジェクト指向の発達とともに多態性を実現するための機能としての側面も持つようになった。利用者定義演算子の定義はどの様な言語でも関数またはメンバー関数あるいはメソッドのいずれかで定義するようになっている。

演算子定義の例[編集]

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においてはメソッドに記号だけで構成されるセレクター[5]をつけることで利用者定義演算子を定義することができる。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++においてはopratorで始まる関数名に記号をつけた関数を定義することで演算子を定義できる。C++では言語機能として用意されている演算子しか定義できず独自の記号を用いた演算子を定義することはできない。また、演算子の引数や戻り値の型は演算子の種類によって制限される。例えば型のメンバーを指定する->演算子を単項演算子として独自に定義したり、数値型や->演算子を定義していない型を戻り値の型として指定することはできない。多くの制限があるなか下記の様な通常の関数と異る演算子独自の振る舞いをする演算子があり通常の関数では不可能な構文を記述することができるようになっている。Smalltalkでは2項演算子しか定義することはできないが+x -xといった単項演算子は勿論x[i]といった添字演算子やx( a, b, c )といった関数呼出演算子なども定義することができる。

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

演算子宣言 名称 振る舞い 応用例
operator 型() 変換演算子 変数に代入される場合や式に組み込まれる場合

演算子を宣言したときの型として振る舞う。

応用例の様にもっぱらifやwhile等の条件文のために用いられる。

Type value0;

int value1 = value0;

// Typeが型変換演算子を実装していれば

// 型変換演算子が呼び出される。

if( Type value( x ) ){}

型 operator->() アロー演算子 戻り値の型として指定した型のメンバーを選択できるようになる。

例ではFunction()を呼び出しているがFunction()はTypeのメンバーではない。

応用例の様なスマートポインターや排他などのためによく用いられる。

Type value;

value->Function();

std::shared_ptr< Vector< int > > value( new Vector<int>() );

vector->push_back( 0 );

使用可能な言語[編集]

独自の演算子定義 種類の制限 多重定義
FORTRAN 可能 なし あり
ALGOL 可能 なし あり
Smalltalk 可能 なし なし
C++ 不可 あり あり
Prolog 可能 なし なし
Haskell 可能 あり なし
OCaml 可能 あり あり
Scala 可能 あり あり
Ruby 不可 あり なし
Python 不可 あり なし
C# 不可 あり あり
Visual Basic .NET 不可 あり あり
PL/SQL 不可 なし あり
PL/pgSQL 不可 なし あり

表内の注記[編集]

独自の演算子定義 組み込みの演算子として存在しない識別子を演算子として定義できる。
種類の制限 標準の演算子の中に再定義できない演算子が存在する。ただし代入式についてはC++以外で定義できる言語は殆どないため制限としていない。
多重定義 演算子として多重定義が可能であること。関数の多重定義ができても演算子は多重定義できない場合もある。

オブジェクト指向と利用者定義演算子[編集]

いくつかのオブジェクト指向言語では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 > struct Traits
{
    typedef Type Vector;
};

template< class Type > struct Traits< Type* >
{
    typedef off_t Vector;
};

template< class Type > struct Traits< std::complex< Type > >
{
    typedef std::complex< Type > Vector;
};

// 中央位置を返す関数
template< class Type > typename Traits< Type >::Vector Center( Type const &edge0, Type const &edge1 )
{
    return ( edge0 + edge1 ) / 2; // 除算演算子の実体はSignの引数によって変わる
}

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;
}

脚注[編集]

  1. ^ https://web.kudpc.kyoto-u.ac.jp/Archives/PDF/NewsLetter/kouhou_f90_3.pdf
  2. ^ http://www.rs.kagu.sut.ac.jp/yama/f90/INTERFACE_.html
  3. ^ https://msdn.microsoft.com/en-us/library/ds533389.aspx
  4. ^ https://docs.oracle.com/database/121/SQLRF/operators007.htm#SQLRF51172
  5. ^ メッセージとメソッドを紐づける名前。Smalltalkでは一つのメソッドに複数のセレクターをつけることができる。

関連項目[編集]