syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

VC++ 2010以降のstd::set::iterator

(これは2011-02-13に書いた故OCNブログの記事を加筆修正したものです)

C++11 (C++0x) 対応関連でSTLのstd::set::iteratorの仕様が変わって、VC++ 2010以降ではstd::set::const_iteratorと同じ型になっています。MS Connectへの報告はすでに存在しますが、これは実際にバグではなく仕様。すなわち、見かけ上は非constiteratorではありますが、iterator経由で変更を加えるような操作ができません。

例えば、VC++ 2008(ISO C++98)では可能だった下記のコードが、VC++ 2010以降ではコンパイルできません。

#include <set>
#include <iostream>
#include <conio.h>

using namespace std;

void main()
{
    set<int> objSet;
    objSet.insert(100);
    set<int>::iterator it = objSet.find(100);
    if (it != objSet.end())
    {
        cout << *it << endl;
        *it = 10; // Oh, God!
        cout << *it << endl; // Oh, Goddess!
    }
    cout << "Press any key..." << endl;
    _getch();
}

まぁできなくなって当然と言えば当然ですね。setやmapはツリーで実装されてて、挿入のたびに自動ソートされるんですが、setの場合キーと値が同じなので、値を書き換えようとするとキーも変わります。ソートのキーがいきなり強制的に書き換えられたら整合性がとれなくなって確かにヤバいよね。むしろコンパイルできていたVC 2008以前(C++98)がおかしかったと言えます。C++03のstd::setもC++98と同じくヤバい仕様になっていると思われます。さすがに上記のような無謀な使い方をする人は居ないと思うんですが、構造体のメンバーのうちのひとつをソートのキーに使って、別のメンバーをiterator経由で変更するようなコードを書いていた場合はこの修正に引っかかります。自分は普段setを使う機会なんてのは滅多にないんですが、今回、他人が書いたコードをVC 2010に移行していたときに遭遇しました。回避するには、一旦eraseしてinsertし直すとか、そもそもsetでなくmapを使うとかしたほうがいいです。

ちなみにC++0x(TR1)のunordered_mapとunordered_setはハッシュ テーブルを使って実装されています。