dlang
D言語
D言語Day 3

便利な演算子オーバーロード(op~系関数)

More than 1 year has passed since last update.


便利な演算子オーバーロード(op~系関数)

「独自で定義した型(=クラス/構造体)でも演算子を使った処理ができたら便利だなぁ」

class MyInt{}

class MyArr{}
MyInt hoge = myInt1.add(myInt2); //面倒
MyInt fuga = myInt1 + myInt2; //出来たら便利
int foo = myArr.index(3); //もうちょっと綺麗に書きたい
int bar = myArr[3]; //出来たらいいな...

→演算子オーバーロードでできる

D言語でできる演算子オーバーロードは沢山あるので書ききれませんが、自分が良いなと思った中から「こんな風に使えばいいかも」とか「ここハマりやすいな」と感じたところをメモしておきます

全部見たい/詳しく見たいという方は、dlang.orgのドキュメントを見るといいです

English:Operator Overloading

日本語(少し古い):演算子オーバーロード


2項演算子(opBinary)

書き方

ReturnType opBinary(string operator)(T val);

テンプレート引数のstring operatorには、演算子の記号が入ります

myClass + anyObj // called opBinary!("+")(anyObj);

myClass * anyObj // called opBinary!("*")(anyObj);
myClass << anyObj // called opBinary!("<<")(anyObj);

実際にはこんな感じで使うことが多いと思います

class myInt{

private int num;
int opBinary(string operator)(int val)
if(operator == "+"){
return this.num + val
}
}

opBinaryで扱える演算子は下記のとおりです

+ - * / % ^^ &

| ^ << >> >>> ~ in

いくつかの処理をまとめて書きたい時はstatic ifを使うと綺麗に書けます

ReturnType opBinary(string op)(T val){

static if(op == "+")
// addition
else static if(op == "-")
// subtraction
else
// other...
}

//複数書くのも可
ReturnType opBinary(string op)(T val)
if(op == "+"){}

ReturnType opBinary(string op)(T val)
if(op == "-"){}

ReturnType opBinary(string op)(T val)
if(op == ">>"){}

ちなみに、Phobos(標準lib)のstd.datetime.Datetimeや同Systimeなどはこれを使って時間の足し引きができるようになっています

import std.datetime;

//Datetime + dur!("seconds") = Datetime
assert(DateTime(2015, 12, 31, 23, 59, 59) + seconds(1) ==
DateTime(2016, 1, 1, 0, 0, 0));
//Systime - dur!("hour") = Systime
assert(SysTime(DateTime(2016, 1, 1, 0, 59, 59)) - hours(1) ==
SysTime(DateTime(2015, 12, 31, 23, 59, 59)));

また、ビットシフト演算子をC++のiostreamのように使っても面白いかもしれません

#include<iostream>

std::cout << "D言語くん可愛い!" << std::endl


等価/不等価演算子(opEquals)

opEqualsで==の挙動を定義すれば、自動的に!=演算子の挙動を決めてくれます

(a != b!(a == b)へコンパイラが変換してくれるようです)

また、クラス同士の等価比較は少し厄介です(後述)

struct Hoge{

bool opEquals(Hoge val){}
bool opEquals(Foo val){}
}
class Foo{
bool opEquals(Hoge val){}
bool opEquals(int num){}
}

特に難しいことは無く、=, !=の処理をオーバーライドできます


クラス同士の比較

D言語のすべてのクラスは、Objectクラスを親に持ちます

そして、ObjectクラスにもopEqualsが定義されています

class Object{

bool opEquals(Object a){}
}

次の例ではコンパイルエラーが出ます

class Hoge{

/*
*Object.opEqualsかHoge.opEqualsのどっちを呼び出せばいいのか、コンパイラが判断できない
*(Hoge.opEqualsの引数がObjectにアップキャストされるため)
*/

bool opEquals(Hoge val){} //NG
}

ので、オーバーライドして引数をキャストしてやります

class Hoge{

override bool opEquals(Object _val){
auto val = cast(Hoge)_val;
//valの処理...
}
}

クラス同士の等価比較だけ少し厄介...


opDispatch

opDispatchは、存在しないメソッドやプロパティを呼び出した際に呼び出される関数です

class Hoge{

void opDispatch(string s)()
{
static if(s == "merryChristmas") writeln("Have a good Christmas!!!");
else writeln("Hoge."~s~"()が呼び出されました");
}
T opDispatch(string s, T)(T val)
{
static if(is(T == int)) return T + 1203;
else static if(is(T == string)) return T~"Happy Christmas"
else return T;
}
}

void main(){
auto hoge = new Hoge();
hoge.hello; //"Hoge.hello()が呼び出されました"
hoge.merryChristmas(); //"Have a good Christmas!!!"
hoge.undefFunc(20160000) // return 20161203(int)
hoge.undefFunc("Yea!") // return "Yea!Happy Christmas"
}

これをうまく使えばRuby on RailsのActiveRecordもどきや、WebAPIのラッパーなどを柔軟に書くことができます

例として

class APIWrapper{

private const BASEURL = "http://example.com/API/";
void opDispatch(string s)(){
//エンドポイント名を直接メソッドのように呼び出す
string url = BASEURL ~ s;
//paramを設定して、送信処理
}
}

これで、http://example.com/API/getMessageobj.getMessage()という風に呼び出せますね

エンドポイントごとにいちいちメソッドを用意しなくていいですし、便利!


opCast

クラスのキャストも独自で定義することができます

class Hoge{

T opCast(T)(){
static if(is(T == int)){}
else static if(is(T == string)){}
else{}
}
}

テンプレートを使えばすっきりと書けます

組込み型や標準ライブラリのクラス/構造体のオレオレ拡張を書くときに、キャストできるようにしておくと互換ができるので便利ですね


まとめ

ここでは4つ挙げましたが、このほかにも


  • 配列みたいに[]を使えるようにする

  • 関数みたいに()で呼び出したりする

  • 単項演算子を使ったり、インクリメント/デクリメントができるようにする

等々、結構たくさんあります

詳しくはdlang.orgを見ると詳しく書いていますが、


  • WebAPIのラッパークラスで、エンドポイントごとにメソッド書くの面倒だしopDispatch使おう

    (例:Twitter4D)

  • 配列のオレオレ拡張クラス書くけど、言語組込みの配列と互換性持たせたいから[]演算子で要素にアクセスできるようにしよう


  • 分数を表すクラス作るけど、小数型にキャストできるようにしておこう

    (例:筆者作分数クラス)

(その他に良い例があればコメントください)

演算子オーバーロードを使うことにより柔軟にコードを書くことができます

楽しい!

次の日はα改さんのLispの話です

ちなみに、自分はマクドでは普段チキンクリスプを頼みます

個人的に、その次の日のatnanasiさんのvibe.dの話とか面白そうだなぁと思ってます

(筆者は元々PHPをメインに書いてる人なので、D言語でweb系でいろいろできたらうれしい...)

楽しみですね!

それでは、少し早いですがMerry Christmas!

良いお年を!