きっかけ
そもそものきっかけは、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
- コンマ演算子をオーバーロードすることが有効な例など。