[C++]アロー演算子オーバーロードでtuple、pairその他一時オブジェクトを返したい場合。

久々の投稿だけど小ネタ。独自クラスのイテレータを設計しているときにちょっと悩んだので。

例えば何らかのイテレータを作っていたとして、そのイテレータの間接演算子が参照型ではなく、何らかの一時オブジェクトを返すものだったとする。

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一時オブジェクトは即時に寿命が尽きるので、firstsecondへアクセスするときには中身は不定値である。したがって、絶対にしてはいけない。

ではどうするのか? 面倒だが、ポインタを代替するクラスを作ることで回避できる。

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の方にもアロー演算子オーバーロードが必要だが、何とかアロー演算子経由のアクセスが可能になる。

何かすごい不細工なので、もっといい方法があったら教えて……。