C++23 <ranges>のviewを見る2 - As rvalue view
views::as_rvalue
views::as_rvalue
は、入力シーケンスの各要素をstd::move()
した右辺値の要素からなるシーケンスを生成するview
です。
import std;
int main() {
std::vector<std::string> strvec = { "move_view", "all_move_view", "as_rvalue_view" };
for (std::string&& rv : strvec | std::views::as_rvalue) {
std::ptrintln("{:s}", rv);
}
// move_view
// all_move_view
// as_rvalue_view
}
このサンプルコードはほぼ意味がなく、これはC++23のranges::to
とともに使用されることを意図しています。
import std;
int main() {
std::vector<std::unique_ptr<int>> upvec;
upvec.emplace_back(std::make_unique<int>(10));
upvec.emplace_back(std::make_unique<int>(100));
upvec.emplace_back(std::make_unique<int>(17));
// listに詰め替え
auto up_list = upvec | std::views::as_rvlaue
| std::ranges::to<std::list>; // ok
// upvecの要素はunique_ptrの左辺値であるため、これだとコピーしようとしてエラー
auto up_list = upvec | std::ranges::to<std::list>; // ng
}
ranges::to
に関してはこちらを参照
同様の事をRangeアルゴリズムでやる時にも使用できます。
import std;
int main() {
std::vector<std::unique_ptr<int>> upvec;
upvec.emplace_back(std::make_unique<int>(10));
upvec.emplace_back(std::make_unique<int>(100));
upvec.emplace_back(std::make_unique<int>(17));
// listに詰め替え
std::list<std::unique_ptr<int>> uplist;
std::ranges::copy(upvec | std::views::as_rvalue, std::back_inserter(uplist));
}
Rangeアダプタオブジェクト
views::as_rvalue
はRangeアダプタオブジェクトであり、入力範囲r
によってviews::as_rvalue(r)
のように呼ばれた時
-
r
の要素型が右辺値である時 :views::all(r)
を返す- 正確には、
r
の右辺値参照型(range_rvalue_reference_t
)とr
の参照型(range_reference_t
)が同じ型である時
- 正確には、
- そうではない(
r
の要素型が左辺値である)時 :as_rvalue_view{r}
を返す
この場合のr
はその値カテゴリに応じて適切にムーブ/コピーされます。
views::as_rvalue
は入力範囲の要素の値カテゴリを見て、それが既に右辺値の場合は何もせず、左辺値である場合にのみas_rvalue_view{r}
を返します。
これによって、既に右辺値の範囲となっている入力範囲に対して要素ごとにstd::move
をかけていくことを回避しており、特に間接参照結果がprvalueになっている範囲に対して使用した場合にコピー省略を妨げないようになっています。
import std;
int main() {
std::string str = "move, all_move, as_rvalue";
// 入力文字列を', 'で分割して、それに何かを付け加えて、stringのvectorに詰める
auto strvec = str | std::views::split(std::string_view{", "})
| std::views::transform([](auto substr) -> std::string {
return "views::" + std::string(std::from_range, substr);
})
| std::views::as_rvalue // 何もしない
| std::ranges::to<std::vector>;
// 2行目のtransformによってstd::stringのprvalueの範囲となっており
// as_rvalueは何もせず、最後のranges::toによるvectorへの挿入ギリギリまでprvalueは実体化しない
for (auto& str : strvec) {
std::println("{:s}", str);
}
}
as_rvalue_view
as_rvalue_view
はviews::as_rvalue
が入力範囲を変換する場合の実装詳細です。とはいえ複雑なものではなく、実体はほとんどmove_iterator
に委譲されています。
// as_rvalue_viewの宣言例
namespace std::ranges {
template<view V>
requires input_range<V>
class as_rvalue_view : public view_interface<as_rvalue_view<V>> {
...
// 非const begin()
constexpr auto begin() requires (!simple-view<V>)
{ return move_iterator(ranges::begin(base_)); }
// 非const end()
constexpr auto end() requires (!simple-view<V>) {
if constexpr (common_range<V>) {
return move_iterator(ranges::end(base_));
} else {
return move_sentinel(ranges::end(base_));
}
}
...
};
}
as_rvalue_view
の存在意義は入力範囲をviews::all
に通して保持しておくところにあります。例えば、move_iterator
とsubrange
でも同じようなものを構成できますが、これだと入力のrange
オブジェクトがダングリングになることがあり、Rangeアダプタとして使用される場合にそれは致命的となるでしょう。
views::as_rvalue
の各種特性
入力の範囲V
の要素型をT
とすると、T
が右辺値である場合は元の範囲と同じ性質を保ち、T
が左辺値である(as_rvalue_view<V>
が使用される場合)場合は次のようになります
-
reference
:T&&
-
range
カテゴリ :input_range
-
common_range
:V
がcommon_range
である時 -
sized_range
:V
がsized_range
である時 -
const-iterable
:V
がconst-iterable
である時 -
borrowed_range
:enable_borrowed_range<V>
がtrue
である時
C++17までの(C++17イテレータとしての)move_iterator<I>
はラップするイテレータI
の性質をなるべく再現しようとしていました。しかし、move_iterator<I>
の各要素は対応するI
のイテレータの要素をムーブしたものであり、同じ要素への2回目以降のアクセスは安全ではありません。
そのため、C++20からの(C++20イテレータとしての)move_iterator<I>
はI
にかかわらず常にinput_iterator
となります。これを受けて、as_rvalue_view
も常にinput_range
となります。
Discussion