⛓️

[C++] <ranges>のviewを見る7 - transform_view

2020/10/19に公開

transform_view

transform_viewは元となるシーケンスの要素それぞれに対して与えられた変換を適用したシーケンスを生成するViewです。

#include <ranges>

int main() {
  // 各要素を2倍する
  std::ranges::transform_view tv{std::views::iota(1, 5), [](int n) { return n * 2; }};
  
  for (int n : tv) {
    std::cout << n; // 2468
  }
}

TのシーケンスとT -> Uへ変換する関数を受けて、Uのシーケンスを返すものです。この例では結果もintですが、型を変換することも当然できます。

transform_viewrangeカテゴリは、元となっているrangeのカテゴリをそのまま継承します。ただし、contiguous rangeにはなりません。

transform_viewfilter_viewと共にrangeアルゴリズムの基礎となるViewであり、多くの所で活用できるでしょう。

遅延評価

transform_viewもまた、遅延評価によってシーケンスを生成します。イテレータの*による参照のタイミングで要素の読み出しと変換の適用が行われます。

// transform_view構築時にはまだ計算されない
std::ranges::transform_view tv{std::views::iota(1, 5), [](int n) { return n * 2; }};

// イテレータ取得時にも計算されない
auto it = std::ranges::begin(tv);

// インクリメント時にも計算されない
++it;

// 間接参照時に要素が読みだされ、変換が適用される
int n1 = *it; // n1 == 2
int n2 = *it; // n2 == 2、再計算している

++it;

int n3 = *it; // n3 == 4

少し注意点なのですが、この性質上*による間接参照のタイミングで毎回変換が行われます。一度計算した値をキャッシュしたりはしません。なので、変換関数としてあまり重い処理を渡すことは避けた方が良いでしょう。

views::transform

transform_viewに対応するrange adaptor objectstd::views::transformです。

#include <ranges>
int main() {
  for (int n : std::views::transform(std::views::iota(1, 5), [](int n) { return n * 2; })) {
    std::cout << n;
  }

  std::cout << '\n';

  // パイプラインスタイル
  for (int n : std::views::iota(1, 5) | std::views::transform([](int n) { return n * 2; })) {
    std::cout << n;
  }
}

views::transformもカスタマイゼーションポインオブジェクトであり、操作を適用するrangeと変換関数を受け取りそれをそのまま転送してtransform_viewを構築して返します。

Discussion