potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++20 std::span、std::basic_string_view、std::ranges::subrangeの使い分け

C++20には所有権を持たず範囲を表すクラスとしてstd::spanstd::basic_string_viewstd::ranges::subrangeが用意されています。これらの概要は以下の通りです。

クラス 概要
std::span 連続したメモリ領域の部分を参照する。
std::basic_string_view 文字列とみなせる連続したメモリ領域の部分を参照する。
std::ranges::subrange イテレーターで先頭と番兵(末尾の次)を指定された範囲を参照する。

std::span

std::spanは連続したメモリ領域、具体的には生の配列、std::arraystd::basic_stringstd::vector等の部分を参照できます。std::listのような連続しないメモリではコンパイルエラーが発生します。

#include <list>
#include <span>
#include <vector>

int main()
{
    // std::vectorはメモリ連続なのでstd::span作成可能
    std::vector<int> vec{ 0, 1, 2, 3, 4 };
    auto vec_span = std::span(vec);
    auto vec_subspan = vec_span.subspan(1, 2);
    // vec_span:    {0, 1, 2, 3, 4}
    // vec_subspan: {1, 2}

    // std::listはメモリ連続ではないのでstd::span作成不可能
    std::list<int> list{ 0, 1, 2, 3, 4 };
    // std::span list_span{ list };

    return 0;
}

std::basic_string_view

std::basic_string_viewstd::spanの文字列版です。std::basic_string_view自体が文字列操作関数(starts_with等)を持ち、またstd::basic_string_viewへのキャストを定義することであるクラスが文字列であることを表現できます。

std::ranges::subrange

std::ranges::subrangeは先頭と番兵(末尾の次)のイテレーターとして受け取り、その間[先頭、番兵)を参照できます。要素の列挙はイテレーターの操作により実行されるため、メモリが連続である必要も反復回数が固定や有限である必要もありません。

#include <list>
#include <ranges>
#include <vector>

int main()
{
    std::vector<int> vec{ 0, 1, 2, 3, 4 };
    std::ranges::subrange vec_all(
        vec.cbegin(),
        vec.cend());
    std::ranges::subrange vec_part(
        std::next(vec.begin(), 1),
        std::next(vec.begin(), 3));
    // vec_all:  {0, 1, 2, 3, 4}
    // vec_part: {1, 2}

    std::list<int> list{ 0, 1, 2, 3, 4 };
    std::ranges::subrange list_all(
        list.cbegin(),
        list.cend());
    std::ranges::subrange list_part(
        std::next(list.begin(), 1),
        std::next(list.begin(), 3));
    // list_all:  {0, 1, 2, 3, 4}
    // list_part: {1, 2}
}

subrangerangeコンセプトを満たすため、rangesビュー(std::views)の処理を適用できます。結果は処理に対応するビューです。

#include <iostream>
#include <list>
#include <ranges>

int main()
{
    std::list<int> list{ 0, 1, 2, 3, 4 };
    std::ranges::subrange list_all(list.cbegin(), list.cend());
    auto list_part = list_all | std::views::drop(2) | std::views::take(2);
    for (const auto& elem : list_part)
        std::wcout << elem << std::endl;
    // 2, 3
}

std::spanstd::basic_string_viewrangeコンセプトを満たすのでrangesビュー(std::views)の処理を適用できます。ただし、結果はstd::spanstd::basic_string_viewではなく、処理に対応するビューです。