読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

日記的な事を書いて行くと思はれる

transformの引数の謎

とあるところでstd::transformの引数は相対性に欠けてよくないという議論を見かけたので少し考えてみました。
先ず前提として、取り敢えずstd::transformは次のように使えます。

std::array<int, 10> a = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
std::array<double, 10> b = {
  9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0
};
std::vector<double> c(10);

// aの要素の二倍をcにコピー
std::transform(a.begin(), a.end(), c.begin(), [](int const x) {
  return x * 2;
});

// aの要素とbの要素の和をcにコピー
std::transform(a.begin(), a.end(), b.begin(), c.begin(), [](int const x, double const y) {
  return y + x;
});

相対性云々というのはaのみにbeginとendの組が与えられているのに対し、bとcはbeginしか渡さないのは気持ち悪いという話だと思います。私も最初妙だなぁと思ったものです。
色々考えてみたのですが、例えばcの部分にbeginとendの組が必要だとするとstd::back_inserterを渡したりする時に不便ということなどが先ず挙がるでしょう。

std::array<int, 10> a = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
std::vector<double> c;

// aの要素の二倍をcに入れていく
std::transform(a.begin(), a.end(), std::back_inserter(c), [](int const x) {
  return x * 2;
});

これで若し変換先にもbeginとendの組が必要だと次のように書かないといけません。

std::array<int, 10> a = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
std::vector<double> c;

// aの要素の二倍をcに入れていく
std::transform(a.begin(), a.end(), std::back_inserter(c), std::back_inserter(), [](int const x) {
  return x * 2;
});

一方でcの部分にbeginとendの組が必要な設計だった場合、若しcの方が範囲が狭かった場合等に適切に処理することが出来るであろうことがメリットとして挙げられます。例えば次のようなコードに対してcの境界を越えてアクセスすることが防がれるであろうということです。

std::array<int, 10> a = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
std::vector<double> c(5);

// aの要素の二倍をcに入れていく
// 多分cの境界越えてアクセスということはなさそう
std::transform(a.begin(), a.end(), c.begin(), c.end(), [](int const x) {
  return x * 2;
});

しかしこれをやろうとすると、endのチェックが一つから二つに増えてしまうであろうことが容易に予想されます。しかもstd::back_inserterの例や、範囲が同じ、或いは大きいものを渡した時の様に、cの境界チェックが必要ない場合も多々あることを鑑みると、余計なオーバーヘッドを避けるために危険はあるもののcの境界チェックは自分でやってくれという、Cからの伝統?的な何かを良くも悪くも受けついだ形になっているのではないでしょうか。とふと思ってみただけです。