#pragma once #include #include #include #include "cast.h" /* * Default implementation of AppendToString uses a temporary TString object which is inefficient. You can overload it * for your type to speed up string joins. If you already have an Out() or operator<<() implementation you can simply * do the following: * * inline void AppendToString(TString& dst, const TMyType& t) { * TStringOutput o(dst); * o << t; * } * * Unfortunately we can't do this by default because for some types ToString() is defined while Out() is not. * For standard types (strings of all kinds and arithmetic types) we don't use a temporary TString in AppendToString(). */ template inline std::enable_if_t>::value, void> AppendToString(TBasicString& dst, const T& t) { dst.AppendNoAlias(ToString(t)); } template inline std::enable_if_t>::value, void> AppendToString(TBasicString& dst, const T& t) { char buf[512]; dst.append(buf, ToString>(t, buf, sizeof(buf))); } template inline void AppendToString(TBasicString& dst, const TCharType* t) { dst.append(t); } template inline void AppendToString(TBasicString& dst, TBasicStringBuf t) { dst.append(t); } namespace NPrivate { template inline size_t GetLength(const T&) { // By default don't pre-allocate space when joining and appending non-string types. // This code can be extended by estimating stringified length for specific types (e.g. 10 for ui32). return 0; } template <> inline size_t GetLength(const TString& s) { return s.length(); } template <> inline size_t GetLength(const TStringBuf& s) { return s.length(); } template <> inline size_t GetLength(const char* const& s) { return (s ? std::char_traits::length(s) : 0); } inline size_t GetAppendLength(const TStringBuf /*delim*/) { return 0; } template size_t GetAppendLength(const TStringBuf delim, const TFirst& f, const TRest&... r) { return delim.length() + ::NPrivate::GetLength(f) + ::NPrivate::GetAppendLength(delim, r...); } } // namespace NPrivate template inline void AppendJoinNoReserve(TBasicString&, TBasicStringBuf) { } template inline void AppendJoinNoReserve(TBasicString& dst, TBasicStringBuf delim, const TFirst& f, const TRest&... r) { AppendToString(dst, delim); AppendToString(dst, f); AppendJoinNoReserve(dst, delim, r...); } template inline void AppendJoin(TString& dst, const TStringBuf delim, const TValues&... values) { const size_t appendLength = ::NPrivate::GetAppendLength(delim, values...); if (appendLength > 0) { dst.reserve(dst.length() + appendLength); } AppendJoinNoReserve(dst, delim, values...); } template inline TString Join(const TStringBuf delim, const TFirst& f, const TRest&... r) { TString ret = ToString(f); AppendJoin(ret, delim, r...); return ret; } // Note that char delimeter @cdelim will be printed as single char string, // but any char value @v will be printed as corresponding numeric code. // For example, Join('a', 'a', 'a') will print "97a97" (see unit-test). template inline TString Join(char cdelim, const TValues&... v) { return Join(TStringBuf(&cdelim, 1), v...); } namespace NPrivate { template inline TBasicString JoinRange(TBasicStringBuf delim, const TIter beg, const TIter end) { TBasicString out; if (beg != end) { size_t total = ::NPrivate::GetLength(*beg); for (TIter pos = beg; ++pos != end;) { total += delim.length() + ::NPrivate::GetLength(*pos); } if (total > 0) { out.reserve(total); } AppendToString(out, *beg); for (TIter pos = beg; ++pos != end;) { AppendJoinNoReserve(out, delim, *pos); } } return out; } } // namespace NPrivate template TString JoinRange(std::string_view delim, const TIter beg, const TIter end) { return ::NPrivate::JoinRange(delim, beg, end); } template TString JoinRange(char delim, const TIter beg, const TIter end) { TStringBuf delimBuf(&delim, 1); return ::NPrivate::JoinRange(delimBuf, beg, end); } template TUtf16String JoinRange(std::u16string_view delim, const TIter beg, const TIter end) { return ::NPrivate::JoinRange(delim, beg, end); } template TUtf16String JoinRange(wchar16 delim, const TIter beg, const TIter end) { TWtringBuf delimBuf(&delim, 1); return ::NPrivate::JoinRange(delimBuf, beg, end); } template TUtf32String JoinRange(std::u32string_view delim, const TIter beg, const TIter end) { return ::NPrivate::JoinRange(delim, beg, end); } template TUtf32String JoinRange(wchar32 delim, const TIter beg, const TIter end) { TUtf32StringBuf delimBuf(&delim, 1); return ::NPrivate::JoinRange(delimBuf, beg, end); } template inline TBasicString JoinSeq(std::basic_string_view delim, const TContainer& data) { using std::begin; using std::end; return JoinRange(delim, begin(data), end(data)); } template inline TBasicString JoinSeq(const TCharType* delim, const TContainer& data) { TBasicStringBuf delimBuf = delim; return JoinSeq(delimBuf, data); } template inline TBasicString JoinSeq(const TBasicString& delim, const TContainer& data) { TBasicStringBuf delimBuf = delim; return JoinSeq(delimBuf, data); } template inline std::enable_if_t< std::is_same_v || std::is_same_v || std::is_same_v, TBasicString> JoinSeq(TCharType delim, const TContainer& data) { TBasicStringBuf delimBuf(&delim, 1); return JoinSeq(delimBuf, data); } /** \brief Functor for streaming iterative objects from TIterB e to TIterE b, separated with delim. * Difference from JoinSeq, JoinRange, Join is the lack of TString object - all depends on operator<< for the type and * realization of IOutputStream */ template struct TRangeJoiner { friend constexpr IOutputStream& operator<<(IOutputStream& stream, const TRangeJoiner& rangeJoiner) { if (rangeJoiner.b != rangeJoiner.e) { stream << *rangeJoiner.b; for (auto it = std::next(rangeJoiner.b); it != rangeJoiner.e; ++it) stream << rangeJoiner.delim << *it; } return stream; } constexpr TRangeJoiner(TStringBuf delim, TIterB&& b, TIterE&& e) : delim(delim) , b(std::forward(b)) , e(std::forward(e)) { } private: const TStringBuf delim; const TIterB b; const TIterE e; }; template constexpr auto MakeRangeJoiner(TStringBuf delim, TIterB&& b, TIterE&& e) { return TRangeJoiner(delim, std::forward(b), std::forward(e)); } template constexpr auto MakeRangeJoiner(TStringBuf delim, const TContainer& data) { return MakeRangeJoiner(delim, std::cbegin(data), std::cend(data)); } template constexpr auto MakeRangeJoiner(TStringBuf delim, const std::initializer_list& data) { return MakeRangeJoiner(delim, std::cbegin(data), std::cend(data)); } /* We force (std::initializer_list) input type for (TString) and (const char*) types because: * # When (std::initializer_list) is used, TString objects are copied into the initializer_list object. * Storing TStringBufs instead is faster, even with COW-enabled strings. * # For (const char*) we calculate length only once and store it in TStringBuf. Otherwise strlen scan would be executed * in both GetAppendLength and AppendToString. For string literals constant lengths get propagated in compile-time. * * This way JoinSeq(",", { s1, s2 }) always does the right thing whatever types s1 and s2 have. * * If someone needs to join std::initializer_list -- it still works because of the TContainer template above. */ template inline std::enable_if_t< !std::is_same, TString>::value && !std::is_same, const char*>::value, TString> JoinSeq(const TStringBuf delim, const std::initializer_list& data) { return JoinRange(delim, data.begin(), data.end()); } inline TString JoinSeq(const TStringBuf delim, const std::initializer_list& data) { return JoinRange(delim, data.begin(), data.end()); }