Posted at

[C++] コンマ演算子はオーバーロードできる、ということを知った

More than 3 years have passed since last update.


きっかけ

そもそものきっかけは、C++における行列やベクトルを扱うライブラリ「Eigen」を使ったことであった。

Eigenでは、行列やベクトルの値を直接指定するために、次のようなコードが使えるのである。

// 各要素がfloat型である3次ベクトル

Vector3f v1;

// 3つの値を設定する
v1 << 1, 2, 3;

(参考:Eigen: Quick reference guide

これを見たとき、最初「コンマは処理を順に行うだけのはずだけど、どうやったら実現できているのだろうか」と気になったのである。で先程ふと「もしかして、コンマ演算子はオーバーロードできる?」と思い至って調べたらまさにその通りであったのである。


簡単なコード例

以下のような使い方ができるクラスValueListを考える。これは`int'の値を以下のように指定できるクラスである。

int main(void){

ValueList foo;

foo << 1, 2, 3, 5, 8, 13, 21;

// 値を順に出力
foo.foreach([](int val){
std::cout << val << std::endl;
});
}

これを実現するためには、方針としては



  • ValueList << valueという演算の結果は、「後ろに, value」を続けられる型(仮にCommaInputとする)でなければならない

  • そのCommaInputもまた、「後ろに, value」を続けられる型でなければならない

ということになる。

まずValueList<<をオペレーターオーバーロードし、CommaInput型を生成するようにしている。

class ValueList{

std::vector<int> values_;

public:
ValueList(){}

CommaInput operator <<(int value){
// values_の中をvalue一つだけにする
values_.assign(1, value);

// その後にコンマ区切りで値が渡せるよう、そのためのクラスを返す
return(CommaInput(*this));
}

template <class LambdaType>
void foreach(const LambdaType & lambda){
for(std::vector<int>::iterator it = values_.begin(); it != values_.end(); ++it){
lambda(*it);
}
}
};

次いでCommaInput。これにValueListへの参照を保持しておき、カンマ区切りで値が与えられたら実際に値の追加を行う。

class CommaInput{

ValueList & vl_;

public:
CommaInput(ValueList & vl) : vl_(vl) {}

// オペレーターオーバーロード
// valueを実際にリストに追加し
// また続けてカンマ区切りで値を指定できるように自身を返す
CommaInput & operator ,(int value){
vl_.values_.push_back(value);
return *this;
}
};

ソースコード全体はこちら。(動かすには、C++11のラムダ式に対応したコンパイラが必要です) https://gist.github.com/maraigue/d7b3029b88b28f6c090d


参考記事



  • C++ tips 3 カンマ演算子編


    • コンマ演算子のよい使い方についての解説だが、最後にコンマ演算子をオーバーロードする話が出ている。「初期化リストの代わりになるような類いのもの実装可能」とする一方、「見た目からはオーバーロードされていることが予測し辛い為、容易くメンテナンス性の悪いコードになってしまいますので、乱用は厳禁です」とも述べている。




  • GESブログ C++でパラメータ配列を実装する


    • 可変長引数に近いものを実装するために、コンマ演算子のオーバーロードを利用する例。




  • c++ - When to Overload the Comma Operator? - Stack Overflow


    • コンマ演算子をオーバーロードすることが有効な例など。