potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

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>等を作成することで回避できます。