久々の投稿だけど小ネタ。独自クラスのイテレータを設計しているときにちょっと悩んだので。
例えば何らかのイテレータを作っていたとして、そのイテレータの間接演算子が参照型ではなく、何らかの一時オブジェクトを返すものだったとする。
struct iterator { std::pair<int&, std::string&> operator*() const { return { *i, *s }; } //他のイテレータっぽい演算子とかは省略する。 int* i; std::string* s; }; int main() { int i = 1; std::string s = "234"; iterator it{ &i, &s }; std::cout << i << " " << s << std::endl; (*it).first = 5; (*it).second = "678"; std::cout << i << " " << s << std::endl; }
iteratorは保有している値への参照をstd::pairへと纏めて返している。std::pair全体では参照でもなんでもない一時オブジェクトだ。
さてこのとき、iteratorにアロー演算子を追加したいと考えた。だがアロー演算子は通常、何らかのオブジェクトへのポインタを返すものだ。ただし今回返したいのはstd::pairの一時オブジェクトであるため、ポインタを返すという行為そのものが不可能である。
struct iterator { //絶対ダメ。 //std::pair<int&, std::string&>* operator->() const { return &std::pair<int&, std::string&>{ i, s }; } };
上のコードでは、アロー演算子によって返されたstd::pair一時オブジェクトは即時に寿命が尽きるので、first
、second
へアクセスするときには中身は不定値である。したがって、絶対にしてはいけない。
ではどうするのか? 面倒だが、ポインタを代替するクラスを作ることで回避できる。
struct pointer_proxy { std::pair<int&, std::string&> x; std::pair<int&, std::string&>* operator->() { return &x; } }; struct iterator { std::pair<int&, std::string&> operator*() const { return { *i, *s }; } pointer_proxy operator->() const { return pointer_proxy{ { *i, *s } }; } int* i; std::string* s; }; int main() { int i = 1; std::string s = "2.34"; iterator it{ &i, &s }; std::cout << i << " " << s << std::endl; it->first = 5; it->second = "6.78"; std::cout << i << " " << s << std::endl; return 0; }
こちらの方法では、一時オブジェクトへのポインタではなくpointer_proxyクラスの一時オブジェクトを返している。面倒なことにpointer_proxyの方にもアロー演算子オーバーロードが必要だが、何とかアロー演算子経由のアクセスが可能になる。
何かすごい不細工なので、もっといい方法があったら教えて……。