potisanのプログラミングメモ

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

C++20 std::views::splitはsubrange型を返す。

std::views::splitの結果をそのままto<std::vector>に渡してコンパイルエラーに悩まされたのでメモとして。

結論としてstd::views::splitの戻り値をstd::ranges::toSTLコンテナに変換するとき、手前で各要素をstd::wstringstd::wstring_viewに変換する必要があります。std::views::split戻り値の各要素はstd::ranges::subrange型で文字列型への直接変換が実装されていないからです。

下記にサンプルコードを示します。丸ごと補助関数にまとめたり、std::wstringstd::wstring_viewへの変換だけ補助関数にしても良いかもしれません。

#include <ranges>
#include <string>
#include <vector>
#include <iostream>

using namespace std::string_view_literals;

int wmain()
{
    auto s1{ L"ABC\0DEF\0GHI\0\0"sv };

    // std::vector<std::wstring>の作成
    auto sv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring(std::ranges::begin(sr), std::ranges::end(sr)); })
        | std::ranges::to<std::vector>() };

    // std::vector<std::wstring_view>の作成
    auto svv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring_view(sr); })
        | std::ranges::to<std::vector>() };

    return -1;
}

std::views::splitの動作は次のコードで確認できます。MSVC 2022であればstd::ranges::split_viewclass _Iterator::value_typesubrangeが明示されているのでSTLソースコードからも確認できます。

#include <ranges>
#include <string>
#include <vector>
#include <iostream>

using namespace std::string_view_literals;

int wmain()
{
    auto s1{ L"ABC\0DEF\0GHI\0\0"sv };

    for (const auto& i : std::views::split(s1, L'\0'))
    {
        // iはstd::ranges::subrange型です。
        // const std::ranges::subrange<std::_String_view_iterator<std::char_traits<wchar_t>>,std::_String_view_iterator<std::char_traits<wchar_t>>,1> &

        // デバッグウィンドウに表示するための操作
        auto j = i;
    }

    for (const auto& i : std::views::split(s1, L'\0'))
    {
        // std::ranges::subrangeはレンジなのでstd::wstring_viewに変換できます。
        std::wstring_view sv(i);
        // std::wstringはレンジからの作成に未対応なのでエラーになります。
        //std::wstring sv(i);
    }

    // 以下をコメントアウトするとコンパイルエラーになります。
    // std::views::splitの戻り値型std::ranges::subrangeはstd::vectorの要素にできません。
    // 下記のようにtransformでstd::wstring_viewやstd::wstringへ変換すれば解決します。
    //for (const auto& i : std::views::split(s1, L'\0') | std::ranges::to<std::vector>())
    //{
    // std::wstring_view sv(i);
    //}

    // std::wstringはレンジからの作成に未対応です。イテレータペアから作成します。
    auto sv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring(std::ranges::begin(sr), std::ranges::end(sr)); })
        | std::ranges::to<std::vector>() };

    // std::wstring_viewはレンジからの作成に対応しています。記述量が減ります。
    // ただし、元の文字列がリテラルでない場合はメモリが解放されると無効になります。
    auto svv1{ std::views::split(s1, L'\0')
        | std::views::transform([](const auto& sr) {return std::wstring_view(sr); })
        | std::ranges::to<std::vector>() };

    return -1;
}

参考