はじめに
最近全然記事を書いていないので小ネタでも記事を書こうと思い筆を取るなどした。
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++で単体テストを書いていると
template<typename T>
struct StringViewSplit : public ::iutest::Test {};
IUTEST_TYPED_TEST_CASE(StringViewSplit, ::iutest::Types<char, wchar_t, char16_t, char32_t>);
型リストを使ったテストを書くこともある。基本的にはこの型リスト全てについてテストが通るように書くのだが、処理系依存な処理を避けるために一時的に一部の型を除外したい時がある。
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時代の対処法
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>
みたいなやつ。これがないと予めすべての文字列型について文字列リテラルをどこかに書いておいて参照するみたいな迂遠なことしないといけない。