C++
C++17

C++17のif constexprがあると嬉しかったシーンを具体的に

はじめに

最近全然記事を書いていないので小ネタでも記事を書こうと思い筆を取るなどした。

if constexprについては、cpprefjpにて
if constexpr 文 - cpprefjp C++日本語リファレンス
を書いたが、未だにC++14時代にも対応したライブラリを作っているのでいまいち使う機会に恵まれなかった。

が、Minecraftというゲームにおいて、鉱石を採掘する際の手法としてチャンク依拠風車型ブランチマイニングというのを@KizahashiLuca氏が提唱しているのだが
チャンク依拠風車型ブランチマイニングの計算
この手法のさらなる研究のためにMinecraftの生成済みワールドのセーブデータであるNBTを読み取ってパースして仮想的に自動採掘する必要ができている。でいりぃ氏という方がJavaでとりあえず作ったのだが(非公開)C++で書いてみたい。
yumetodo / branchminingsimulator — Bitbucket
しかし文字列を操作する必要がある。それもstd::basic_string_viewを使ってメモリーアロケーションを可能な限り排除して。

というわけで、
C++でPStade.Oven(pipeline)風味なstringのsplitを作ってみた
で紹介した文字列分割ライブラリをstd::basic_string_viewに対応させる必要が出てきた。
@kazatsuyu 氏にコメントで指摘されていた件に重い腰を上げて動き始めたわけだ。

マクロで環境を判定して自動でstd::basic_string_viewが使えれば対応するように目下製作中だが
Feat: support string_view by yumetodo · Pull Request #5 · yumetodo/string_split
std::basic_string_viewが使えるならそういえばif constexprが使える。ということはテストケースが少しだけシンプルに書ける。そんなことがあった。

状況

C++で単体テストを書いていると

例はiutestをテストフレームワークに使用
template<typename T>
struct StringViewSplit : public ::iutest::Test {};
IUTEST_TYPED_TEST_CASE(StringViewSplit, ::iutest::Types<char, wchar_t, char16_t, char32_t>);

型リストを使ったテストを書くこともある。基本的にはこの型リスト全てについてテストが通るように書くのだが、処理系依存な処理を避けるために一時的に一部の型を除外したい時がある。

C++14時代の対処法

C++14
namespace StringSplitLvalue_SplitByStlStr {
    template<typename CharType, std::enable_if_t<!std::is_same<CharType, char>::value, std::nullptr_t> = nullptr>
    void without_char()
    {
        using char_type = CharType;
        const std::basic_string<char_type> s2 = constant::arikitarina_sekai_wspace<char_type>();
        const std::basic_string<char_type> re2_1[] = { constant::arikitarina<char_type>(), constant::sekai<char_type>() };
        const auto re2_2 = s2 | split(std::basic_string<char_type>(constant::wspace<char_type>()));
        IUTEST_ASSERT_TRUE(std::equal(std::begin(re2_1), std::end(re2_1), re2_2.begin(), re2_2.end()));
    }
    template<typename CharType, std::enable_if_t<std::is_same<CharType, char>::value, std::nullptr_t> = nullptr>
    void without_char() {}
}
IUTEST_TYPED_TEST(StringSplitLvalue, SplitByStlStr)
{
    using char_type = TypeParam;
    const std::basic_string<char_type> s1 = constant::arikitari_na_world_underscore<char_type>();
    const std::basic_string<char_type> re1_1[] = { constant::arikitari<char_type>(), constant::na<char_type>(), constant::world<char_type>() };
    const auto re1_2 = s1 | split(std::basic_string<char_type>(constant::space_underscore<char_type>()));
    IUTEST_ASSERT_TRUE(std::equal(std::begin(re1_1), std::end(re1_1), re1_2.begin(), re1_2.end()));
    StringSplitLvalue_SplitByStlStr::without_char<char_type>();
}

SFINAEで場合分けするヘルパー関数を作ることになる。

いや、今回の例ならtemplate関数の完全特殊化でもタグディスパッチでもいいけど。

いずれにせよテストケースの外に書かないといけなくて辛い。

C++17時代の対処法

C++17
IUTEST_TYPED_TEST(StringViewSplit, SplitByStlStr)
{
    using char_type = TypeParam;
    const std::basic_string_view<char_type> s1 = constant::arikitari_na_world_underscore<char_type>();
    const std::basic_string_view<char_type> re1_1[] = { constant::arikitari<char_type>(), constant::na<char_type>(), constant::world<char_type>() };
    const auto re1_2 = s1 | split(std::basic_string<char_type>(constant::space_underscore<char_type>()));
    IUTEST_ASSERT_TRUE(std::equal(std::begin(re1_1), std::end(re1_1), re1_2.begin(), re1_2.end()));
    if constexpr(!std::is_same_v<char_type, char>) {
        const std::basic_string_view<char_type> s2 = constant::arikitarina_sekai_wspace<char_type>();
        const std::basic_string_view<char_type> re2_1[] = { constant::arikitarina<char_type>(), constant::sekai<char_type>() };
        const auto re2_2 = s2 | split(std::basic_string<char_type>(constant::wspace<char_type>()));
        IUTEST_ASSERT_TRUE(std::equal(std::begin(re2_1), std::end(re2_1), re2_2.begin(), re2_2.end()));
    }
}

すっきり。ついでにstd::is_same_vも使える。

余談

それはそうと文字列リテラルにtemplateパラメータがほしい。

"arikitari na sekai"<char16_t>

みたいなやつ。これがないと予めすべての文字列型について文字列リテラルをどこかに書いておいて参照するみたいな迂遠なことしないといけない。