前に作ったC++でPStade.Oven(pipeline)風味なstringのsplitするやつをstring_viewに対応させた

# はじめに

[C++17のif constexprがあると嬉しかったシーンを具体的に](https://qiita.com/yumetodo/items/c683acfa571dd5f9306a)
の記事でも言ったとおり、諸事情あって
前に
[C++でPStade.Oven(pipeline)風味なstringのsplitを作ってみた](https://qiita.com/yumetodo/items/bf2bc5c1d49d5aec3efa)
作ったC++でPStade.Oven(pipeline)風味なstringのsplitするやつをstd::basic_string_viewに対応させる必要が出てきた。
@kazatsuyu 氏にコメントで指摘されていた件に

>それはともかく、せっかく遅延評価でオーバーヘッドを減らしたので、いっそのことlvalue受け取った時はC++17で標準入り予定のstd::basic_string_viewを使ってコピーのオーバーヘッドもなくせるようにしましょうそうしましょう。

重い腰を上げて動き始めたわけだ。

# 結果

基本的にこのライブラリは

```
文字列 | split(区切り文字or区切りの集合文字列)/*あとこの後ろに色々つけれる*/
```

のように使うわけで、つまり元となる文字列と区切りの集合文字列双方に`std::basic_string_view`が使えるようになった。

```cpp
#include "../include/string_split.hpp"
#include <iostream>
int main()
{
    using namespace std::literals;
    std::string_view s1 = "arikitari na world!";
    const auto s_1 = s1 | split(' ')[1];//"na"
    std::string s2 = "arikitari na_world!";
    s2 | split(" _"sw) >> [](std::string&& s) {
        std::cout << s << std::endl;
    };
    /*stdout:
    arikitari 
    na
    world!
    */
}
```

# 対応方法

VS2015だとtemplate引数でのSFINAEが若干バグ踏みやすいのでC++14版は戻り値でSFINAEする。このためSTRING_SPLIT_HAS_CXX17_STRING_VIEWマクロを以下のように作って実装を切り替える。

```cpp
#if defined(_MSC_VER)
#	if _MSC_VER >= 1910 && _MSVC_LANG >= 201703
#		define STRING_SPLIT_HAS_CXX17_STRING_VIEW 1
#	endif
#elif defined(__clang__)
#	if __clang_major__ >= 4 && __cplusplus >= 201703
#		define STRING_SPLIT_HAS_CXX17_STRING_VIEW 1
#	endif
#elif defined(__GNUC__)
#	if (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 1)) && __cplusplus >= 201703
#		define STRING_SPLIT_HAS_CXX17_STRING_VIEW 1
#	endif
#endif
```

つまり

```cpp
	//区切り文字複数の時
#ifdef STRING_SPLIT_HAS_CXX17_STRING_VIEW
	template<
		typename StrType, typename DelimType, bool is_c_str, bool is_stl_string,
		enable_if_t<type_traits::contract_str_type_and_delim_type_without_single_char_v<StrType, DelimType, is_c_str, is_stl_string>, std::nullptr_t> = nullptr
	>
	auto operator| (const StrType& str, const split_helper<DelimType, false, is_c_str, is_stl_string>& info) -> vector<StrType>
	{
#else
	template<typename CharType, typename DelimType, bool is_c_str, bool is_stl_string>
	auto operator| (const b_str<CharType>& str, const split_helper<DelimType, false, is_c_str, is_stl_string>& info)
		-> enable_if_t<
			type_traits::contract_delim_type_without_single_char<CharType, DelimType, is_c_str, is_stl_string>::value,
			vector<b_str<CharType>>
		>
	{
		using StrType = b_str<CharType>;
#endif
		vector<StrType> re;
		size_t current = 0;
		for (
			size_t found = str.find_first_of(info.delim, current);
			current != StrType::npos && found != StrType::npos;
			current = str.find_first_not_of(info.delim, found + 1), found = str.find_first_of(info.delim, current)
		) {
			if (re.capacity() < re.size() + 1) re.reserve((std::numeric_limits<size_t>::max() / 2 < re.size()) ? std::numeric_limits<size_t>::max() : re.size() * 2);
			vector_emplace_make_substr(re, str, current, found - current);
		}
		vector_emplace_make_substr(re, str, current, str.size() - current);
		return re;
	}
```

のようにtemplate引数やら引数を書くところを切り替える。

結果として実装部分よりはるかに長くなってしまったぜ・・・やばい。とくにこいつ

```cpp
	//区切り文字複数, has chain convert funcの時
#ifdef STRING_SPLIT_HAS_CXX17_STRING_VIEW
	template<
		typename StrType, typename DelimType, typename FuncType,
		bool is_c_str, bool is_stl_string,
		enable_if_t<conjunction_v<
			type_traits::contract_str_type_and_delim_type_without_single_char<StrType, DelimType, is_c_str, is_stl_string>,
			type_traits::negation<is_void<invoke_result_t<FuncType, StrType>>>
		>, std::nullptr_t> = nullptr
	>
	auto operator| (const StrType& str, const split_helper_conv_func<DelimType, FuncType, false, is_c_str, is_stl_string>& info)
		-> vector<invoke_result_t<FuncType, StrType>>
	{
#else
	template<
		typename CharType, typename DelimType, typename FuncType,
		bool is_c_str, bool is_stl_string
	>
	auto operator| (const b_str<CharType>& str, const split_helper_conv_func<DelimType, FuncType, false, is_c_str, is_stl_string>& info)
		-> enable_if_t<
			type_traits::conjunction<
				type_traits::contract_delim_type_without_single_char<CharType, DelimType, is_c_str, is_stl_string>,
				type_traits::negation<is_void<invoke_result_t<FuncType, b_str<CharType>>>>
			>::value,
			vector<invoke_result_t<FuncType, b_str<CharType>>>
		>
	{
		using StrType = b_str<CharType>;
#endif
		vector<invoke_result_t<FuncType, StrType>> re;
		size_t current = 0;
		for (
			size_t found = str.find_first_of(info.delim, current);
			current != StrType::npos && found != StrType::npos;
			current = str.find_first_not_of(info.delim, found + 1), found = str.find_first_of(info.delim, current)
		) {
			if (re.capacity() < re.size() + 1) re.reserve((std::numeric_limits<size_t>::max() / 2 < re.size()) ? std::numeric_limits<size_t>::max() : re.size() * 2);
			re.emplace_back(info.f(str.substr(current, found - current)));
		}
		re.emplace_back(info.f(str.substr(current, str.size() - current)));
		return re;
	}
```

SFINAEする箇所が長すぎる。つらい。

とにかくそんなやつを書いていた。

# 同時に行った他の作業

最初作ったことはcmakeスキルが低すぎてGNUMakeとvs両対応させるのにそれぞれ設定を書いていたが、cmake力も上がったし、メンテだるいわでcmakeに書き換えた。

coverageをcmakeからやらせるのに
https://github.com/bilke/cmake-modules/blob/master/CodeCoverage.cmake
からコードを拝借して改造した( [`8fbe6ef`](https://github.com/yumetodo/string_split/pull/5/commits/8fbe6efcc4446895a2721a9dac6c41fd58d1dcc0))。
もとのコードではgenhtmlをつねに呼び出すようになっていたが、genhtmlはCIでは呼び出しても意味がないというかそういうのはcoveralls.ioにやらしているので、そこを分離した。

coveralls.ioに結果を送るのにlcoverallsコマンドを使うわけだが、前はMakefileの中で呼んでいた。これをtravisのscriptの中で直に呼び出すように書き換えた。どうせそこ以外では使わん。

ただこのときTravisのカレントディレクトリがどう変遷するか意味不明でやや苦戦した。
というかTravisにかぎらずCIってカレントディレクトリがどこかまじでいっつもわからん。
結局どうにか[`aa8d6a6`](https://github.com/yumetodo/string_split/pull/5/commits/aa8d6a6714f653d0ae29e81db49ccf164f459cf1)で解決した。

# まとめ

https://github.com/yumetodo/string_split/pull/5
70commit(+2,385 −1,850)にわたる大きな変更となった。

```
$ cat ./include/string_split.hpp | wc -l
952
$ cat ./test/*.cpp | wc -l
1414
```

# License

[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.ja)

<a href="https://commons.wikimedia.org/wiki/File:CC-BY_icon.svg#/media/File:CC-BY_icon.svg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/CC-BY_icon.svg/1200px-CC-BY_icon.svg.png" alt="CC-BY icon.svg" width="88px"></a>