はじめに
C++17のif constexprがあると嬉しかったシーンを具体的に
の記事でも言ったとおり、諸事情あって
前に
C++でPStade.Oven(pipeline)風味なstringのsplitを作ってみた
作ったC++でPStade.Oven(pipeline)風味なstringのsplitするやつをstd::basic_string_viewに対応させる必要が出てきた。
@kazatsuyu 氏にコメントで指摘されていた件に
それはともかく、せっかく遅延評価でオーバーヘッドを減らしたので、いっそのことlvalue受け取った時はC++17で標準入り予定のstd::basic_string_viewを使ってコピーのオーバーヘッドもなくせるようにしましょうそうしましょう。
重い腰を上げて動き始めたわけだ。
結果
基本的にこのライブラリは
文字列 | split(区切り文字or区切りの集合文字列)/*あとこの後ろに色々つけれる*/
のように使うわけで、つまり元となる文字列と区切りの集合文字列双方にstd::basic_string_view
が使えるようになった。
#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マクロを以下のように作って実装を切り替える。
#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
つまり
//区切り文字複数の時
#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引数やら引数を書くところを切り替える。
結果として実装部分よりはるかに長くなってしまったぜ・・・やばい。とくにこいつ
//区切り文字複数, 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
)。
もとのコードではgenhtmlをつねに呼び出すようになっていたが、genhtmlはCIでは呼び出しても意味がないというかそういうのはcoveralls.ioにやらしているので、そこを分離した。
coveralls.ioに結果を送るのにlcoverallsコマンドを使うわけだが、前はMakefileの中で呼んでいた。これをtravisのscriptの中で直に呼び出すように書き換えた。どうせそこ以外では使わん。
ただこのときTravisのカレントディレクトリがどう変遷するか意味不明でやや苦戦した。
というかTravisにかぎらずCIってカレントディレクトリがどこかまじでいっつもわからん。
結局どうにかaa8d6a6
で解決した。
まとめ
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