std::setのinsertが返すiteratorについて

std::setに要素を追加するコードとして以下のようなものを書いてみた.

#include <iostream>
#include <set>

struct Element
{
  Element(int const& arg1, int const& arg2)
    : first(arg1), second(arg2) {}

  int first;
  int second;
};

inline bool operator<(Element const& lhs, Element const& rhs)
{
  return lhs.first < rhs.first;
}

int main()
{
  std::set<Element> container;
  Element elem1(0, 1);
  container.insert(elem1);
  std::cout << "fisrt=" << elem1.first
            << ", second=" << elem1.second << std::endl;
  std::cout << "fisrt=" << (*container.begin()).first
            << ", second=" << (*container.begin()).second << std::endl;
}

これはちゃんと動作して, "fisrt=0, second=1"のような結果が得られる.

次に要素を挿入すると同時にsecondの値を書き換えるようにコードを変更してみる. この際, 挿入してから再度要素を見つけ出すのは無駄だと考え, insertの返り値を利用する.

std::pair<std::set<Element>::iterator, bool> ret(container.insert(elem1));
(*ret.first).second = 10;

http://www.cplusplus.com/reference/set/set/insert/

しかしながら, これは通らない. insertはiteratorを返すと書いてあるにも関わらず, const_iteratorが返ってきているようである. 実際その通りで, std::setではiteratorは実質const_iteratorであるらしい. 良く考えると当然の道理でここでiteratorに対して値を変更されるとstd::set内での順序がおかしくなる恐れがあるからである.

逆に言えば今回の場合, 変更を試みたメンバ変数secondはElementの順序関係に影響を及ぼさないため, この限りではないと言える. その場合に限ってmutableを使えば問題ない.

http://www.geocities.jp/ky_webid/cpp/language/020.html

struct Element
{
  Element(int const& arg1, int const& arg2)
    : first(arg1), second(arg2) {}

  int first;
  mutable int second;
};

これで問題なく動作する.

$ g++ main.cpp && ./a.out 
fisrt=0, second=1
fisrt=0, second=10

mutableをつけたことでconst_iteratorに対してでもsecondが変更可能になった. こうなってくるとconstって何だ?という気持ちにもなるが, 初めてmutableを使う場面に出くわしたので記録しておく.

http://d.hatena.ne.jp/matobow/20111129/p1

蛇足だが, Elementのfirstの方をmutableにして, 下記のコードを実行すると順序がおかしな事になっていることがわかる.

int main()
{
  std::set<Element> container;
  Element elem1(1, 1), elem2(2, 2);

  container.insert(elem1);
  std::pair<std::set<Element>::iterator, bool> ret(container.insert(elem2));
  (*ret.first).first = 0;

  for (std::set<Element>::const_iterator i(container.begin());
    i != container.end(); ++i)
  {
     std::cout << "fisrt=" << (*i).first
               << ", second=" << (*i).second << std::endl;
  }
}
$ g++ main.cpp && ./a.out 
fisrt=1, second=1
fisrt=0, second=2

なんとも気持ちが悪いが, 仕方が無いのかもしれない.