potisanのプログラミングメモ

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

C++17-20 文字列をある文字で分割した文字列ビューのベクターを作成する

C++で文字列をある文字で分割した文字列ビューのベクターを作成するコードです。コンセプトを使用したC++20以降用のコード、std::basic_string_viewを使用したC++17以降用のコードを記載しています。

C++20以降用のコード

あまり使う機会はありませんがbasic_stringbasic_string_viewはテンプレート引数で文字列特性を指定できるため、その対応のためにコンセプトを使用しています。

#include <string_view>
#include <vector>

template <typename STRING_TYPE, typename CHAR_TRAITS = std::char_traits<wchar_t>>
concept wstring_viewable = std::constructible_from<std::basic_string_view<wchar_t, CHAR_TRAITS>, STRING_TYPE>;

// 文字列ビューを文字で分割した文字列ビューのベクターを作成します。
// C++20以降で使用可能です。
template <typename CHAR_TRAITS = std::char_traits<wchar_t>, wstring_viewable<CHAR_TRAITS> T>
auto split_wstring_view_by_delimiter_as_vector(const T& string, wchar_t delimiter)
{
    using STRING_VIEW_TYPE = std::basic_string_view<wchar_t, CHAR_TRAITS>;
    STRING_VIEW_TYPE sv{ string };
    std::vector<STRING_VIEW_TYPE> result;
    const auto len = sv.length();
    size_t i = 0;
    for (;;)
    {
        const auto j = sv.find(delimiter, i);
        result.emplace_back(sv.substr(i, j - i));
        if (j == STRING_VIEW_TYPE::npos) {
            break;
        }
        i = j + 1;
    }
    return std::move(result);
}

int main()
{
    // 文字列リテラルは静的領域に確保されるため、文字列ビューは常に有効です。
    auto svv1 = split_wstring_view_by_delimiter_as_vector(L"ABC.DEF", L'.');
    auto svv2 = split_wstring_view_by_delimiter_as_vector(L".ABC.DEF.", L'.');
    auto svv3 = split_wstring_view_by_delimiter_as_vector(L"..ABC.DEF..", L'.');
    // svv1: {"ABC", "DEF"}
    // svv2: {"", "ABC", "DEF", ""}
    // svv3: {"", "", "ABC", "DEF", "", ""}

    // std::wstringは動的に確保されるため、破壊後の文字列ビューは無効です。
    std::wstring ws1{ L"ABC.DEF" };
    auto svv4 = split_wstring_view_by_delimiter_as_vector(ws1, L'.');
    // svv4: {"ABC", "DEF"}
    ws1.clear();
    // svv4: {<無効な値>, <無効な値>}

    // 上記は新しい文字列のベクトル等に複製すれば回避できます(メモリは消費します)。
    std::wstring ws2{ L"ABC.DEF" };
    auto svv5 = split_wstring_view_by_delimiter_as_vector(ws2, L'.');
    std::vector<std::wstring> sv1(svv5.cbegin(), svv5.cend());
    ws2.clear();
    // sv1: {"ABC", "DEF"}

    return 0;
}

C++17以降のコード

#include <string_view>
#include <vector>

// 文字列ビューを文字で分割した文字列ビューのベクターを作成します。
// C++17以降で使用可能です。
template <typename CHAR_TRAITS = std::char_traits<wchar_t>>
auto split_wstring_view_by_delimiter_as_vector(
    std::basic_string_view<wchar_t, CHAR_TRAITS> sv,
    wchar_t delimiter)
{
    using STRINGVIEW_TYPE = std::basic_string_view<wchar_t, CHAR_TRAITS>;
    std::vector<STRINGVIEW_TYPE> result;
    const auto len = sv.length();
    size_t i = 0;
    for (;;)
    {
        const auto j = sv.find(delimiter, i);
        result.emplace_back(sv.substr(i, j - i));
        if (j == STRINGVIEW_TYPE::npos) {
            break;
        }
        i = j + 1;
    }
    return std::move(result);
}

int main()
{
    using namespace std::string_view_literals;

    // 文字列リテラルは静的領域に確保されるため、文字列ビューは常に有効です。
    auto svv1 = split_wstring_view_by_delimiter_as_vector(L"ABC.DEF"sv, L'.');
    auto svv2 = split_wstring_view_by_delimiter_as_vector(L".ABC.DEF."sv, L'.');
    auto svv3 = split_wstring_view_by_delimiter_as_vector(L"..ABC.DEF.."sv, L'.');
    // svv1: {"ABC", "DEF"}
    // svv2: {"", "ABC", "DEF", ""}
    // svv3: {"", "", "ABC", "DEF", "", ""}

    // std::wstringは動的に確保されるため、破壊後の文字列ビューは無効です。
    std::wstring ws1{ L"ABC.DEF" };
    auto svv4 = split_wstring_view_by_delimiter_as_vector(std::wstring_view{ ws1 }, L'.');
    // svv4: {"ABC", "DEF"}
    ws1.clear();
    // svv4: {<無効な値>, <無効な値>}

    // 上記は新しい文字列のベクトル等に複製すれば回避できます(メモリは消費します)。
    std::wstring ws2{ L"ABC.DEF" };
    auto svv5 = split_wstring_view_by_delimiter_as_vector(std::wstring_view{ ws2 }, L'.');
    std::vector<std::wstring> sv1(svv5.cbegin(), svv5.cend());
    ws2.clear();
    // sv1: {"ABC", "DEF"}

    return 0;
}

注意

上記の関数は戻り値にstd::wstring_viewを使うので多様なクラスに適用できてメモリ消費も少ないですが、元の文字列が動的に確保された場合(std::wstringwinrt::hstringwil::unique_something_string等)、その文字列の解放後に取得した文字列ビューが無効となることに注意してください。これは文字列ビューベクトルのイテレーターでstd::vector<std::wstring>等を作成することで回避できます。