// // Copyright 2017 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // ----------------------------------------------------------------------------- // File: str_cat.h // ----------------------------------------------------------------------------- // // This package contains functions for efficiently concatenating and appending // strings: `StrCat()` and `StrAppend()`. Most of the work within these routines // is actually handled through use of a special AlphaNum type, which was // designed to be used as a parameter type that efficiently manages conversion // to strings and avoids copies in the above operations. // // Any routine accepting either a string or a number may accept `AlphaNum`. // The basic idea is that by accepting a `const AlphaNum &` as an argument // to your function, your callers will automagically convert bools, integers, // and floating point values to strings for you. // // NOTE: Use of `AlphaNum` outside of the //absl/strings package is unsupported // except for the specific case of function parameters of type `AlphaNum` or // `const AlphaNum &`. In particular, instantiating `AlphaNum` directly as a // stack variable is not supported. // // Conversion from 8-bit values is not accepted because, if it were, then an // attempt to pass ':' instead of ":" might result in a 58 ending up in your // result. // // Bools convert to "0" or "1". Pointers to types other than `char *` are not // valid inputs. No output is generated for null `char *` pointers. // // Floating point numbers are formatted with six-digit precision, which is // the default for "std::cout <<" or printf "%g" (the same as "%.6g"). // // You can convert to hexadecimal output rather than decimal output using the // `Hex` type contained here. To do so, pass `Hex(my_int)` as a parameter to // `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using // a `PadSpec` enum. // // User-defined types can be formatted with the `AbslStringify()` customization // point. The API relies on detecting an overload in the user-defined type's // namespace of a free (non-member) `AbslStringify()` function as a definition // (typically declared as a friend and implemented in-line. // with the following signature: // // class MyClass { ... }; // // template // void AbslStringify(Sink& sink, const MyClass& value); // // An `AbslStringify()` overload for a type should only be declared in the same // file and namespace as said type. // // Note that `AbslStringify()` also supports use with `absl::StrFormat()` and // `absl::Substitute()`. // // Example: // // struct Point { // // To add formatting support to `Point`, we simply need to add a free // // (non-member) function `AbslStringify()`. This method specifies how // // Point should be printed when absl::StrCat() is called on it. You can add // // such a free function using a friend declaration within the body of the // // class. The sink parameter is a templated type to avoid requiring // // dependencies. // template friend void AbslStringify(Sink& // sink, const Point& p) { // absl::Format(&sink, "(%v, %v)", p.x, p.y); // } // // int x; // int y; // }; // ----------------------------------------------------------------------------- #ifndef ABSL_STRINGS_STR_CAT_H_ #define ABSL_STRINGS_STR_CAT_H_ #include #include #include #include #include #include #include #include #include #include #include #include "absl/base/attributes.h" #include "absl/base/nullability.h" #include "absl/base/port.h" #include "absl/meta/type_traits.h" #include "absl/strings/has_absl_stringify.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace strings_internal { // AlphaNumBuffer allows a way to pass a string to StrCat without having to do // memory allocation. It is simply a pair of a fixed-size character array, and // a size. Please don't use outside of absl, yet. template struct AlphaNumBuffer { std::array data; size_t size; }; } // namespace strings_internal // Enum that specifies the number of significant digits to return in a `Hex` or // `Dec` conversion and fill character to use. A `kZeroPad2` value, for example, // would produce hexadecimal strings such as "0a","0f" and a 'kSpacePad5' value // would produce hexadecimal strings such as " a"," f". enum PadSpec : uint8_t { kNoPad = 1, kZeroPad2, kZeroPad3, kZeroPad4, kZeroPad5, kZeroPad6, kZeroPad7, kZeroPad8, kZeroPad9, kZeroPad10, kZeroPad11, kZeroPad12, kZeroPad13, kZeroPad14, kZeroPad15, kZeroPad16, kZeroPad17, kZeroPad18, kZeroPad19, kZeroPad20, kSpacePad2 = kZeroPad2 + 64, kSpacePad3, kSpacePad4, kSpacePad5, kSpacePad6, kSpacePad7, kSpacePad8, kSpacePad9, kSpacePad10, kSpacePad11, kSpacePad12, kSpacePad13, kSpacePad14, kSpacePad15, kSpacePad16, kSpacePad17, kSpacePad18, kSpacePad19, kSpacePad20, }; // ----------------------------------------------------------------------------- // Hex // ----------------------------------------------------------------------------- // // `Hex` stores a set of hexadecimal string conversion parameters for use // within `AlphaNum` string conversions. struct Hex { uint64_t value; uint8_t width; char fill; template explicit Hex( Int v, PadSpec spec = absl::kNoPad, typename std::enable_if::value>::type* = nullptr) : Hex(spec, static_cast(v)) {} template explicit Hex( Int v, PadSpec spec = absl::kNoPad, typename std::enable_if::value>::type* = nullptr) : Hex(spec, static_cast(v)) {} template explicit Hex( Int v, PadSpec spec = absl::kNoPad, typename std::enable_if::value>::type* = nullptr) : Hex(spec, static_cast(v)) {} template explicit Hex( Int v, PadSpec spec = absl::kNoPad, typename std::enable_if::value>::type* = nullptr) : Hex(spec, static_cast(v)) {} template explicit Hex(absl::Nullable v, PadSpec spec = absl::kNoPad) : Hex(spec, reinterpret_cast(v)) {} template friend void AbslStringify(S& sink, Hex hex) { static_assert( numbers_internal::kFastToBufferSize >= 32, "This function only works when output buffer >= 32 bytes long"); char buffer[numbers_internal::kFastToBufferSize]; char* const end = &buffer[numbers_internal::kFastToBufferSize]; auto real_width = absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16); if (real_width >= hex.width) { sink.Append(absl::string_view(end - real_width, real_width)); } else { // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and // max pad width can be up to 20. std::memset(end - 32, hex.fill, 16); // Patch up everything else up to the real_width. std::memset(end - real_width - 16, hex.fill, 16); sink.Append(absl::string_view(end - hex.width, hex.width)); } } private: Hex(PadSpec spec, uint64_t v) : value(v), width(spec == absl::kNoPad ? 1 : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2 : spec - absl::kZeroPad2 + 2), fill(spec >= absl::kSpacePad2 ? ' ' : '0') {} }; // ----------------------------------------------------------------------------- // Dec // ----------------------------------------------------------------------------- // // `Dec` stores a set of decimal string conversion parameters for use // within `AlphaNum` string conversions. Dec is slower than the default // integer conversion, so use it only if you need padding. struct Dec { uint64_t value; uint8_t width; char fill; bool neg; template explicit Dec(Int v, PadSpec spec = absl::kNoPad, typename std::enable_if<(sizeof(Int) <= 8)>::type* = nullptr) : value(v >= 0 ? static_cast(v) : uint64_t{0} - static_cast(v)), width(spec == absl::kNoPad ? 1 : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2 : spec - absl::kZeroPad2 + 2), fill(spec >= absl::kSpacePad2 ? ' ' : '0'), neg(v < 0) {} template friend void AbslStringify(S& sink, Dec dec) { assert(dec.width <= numbers_internal::kFastToBufferSize); char buffer[numbers_internal::kFastToBufferSize]; char* const end = &buffer[numbers_internal::kFastToBufferSize]; char* const minfill = end - dec.width; char* writer = end; uint64_t val = dec.value; while (val > 9) { *--writer = '0' + (val % 10); val /= 10; } *--writer = '0' + static_cast(val); if (dec.neg) *--writer = '-'; ptrdiff_t fillers = writer - minfill; if (fillers > 0) { // Tricky: if the fill character is ' ', then it's <+/-> // But...: if the fill character is '0', then it's <+/-> bool add_sign_again = false; if (dec.neg && dec.fill == '0') { // If filling with '0', ++writer; // ignore the sign we just added add_sign_again = true; // and re-add the sign later. } writer -= fillers; std::fill_n(writer, fillers, dec.fill); if (add_sign_again) *--writer = '-'; } sink.Append(absl::string_view(writer, static_cast(end - writer))); } }; // ----------------------------------------------------------------------------- // AlphaNum // ----------------------------------------------------------------------------- // // The `AlphaNum` class acts as the main parameter type for `StrCat()` and // `StrAppend()`, providing efficient conversion of numeric, boolean, decimal, // and hexadecimal values (through the `Dec` and `Hex` types) into strings. // `AlphaNum` should only be used as a function parameter. Do not instantiate // `AlphaNum` directly as a stack variable. class AlphaNum { public: // No bool ctor -- bools convert to an integral type. // A bool ctor would also convert incoming pointers (bletch). AlphaNum(int x) // NOLINT(runtime/explicit) : piece_(digits_, static_cast( numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0])) {} AlphaNum(unsigned int x) // NOLINT(runtime/explicit) : piece_(digits_, static_cast( numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0])) {} AlphaNum(long x) // NOLINT(*) : piece_(digits_, static_cast( numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0])) {} AlphaNum(unsigned long x) // NOLINT(*) : piece_(digits_, static_cast( numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0])) {} AlphaNum(long long x) // NOLINT(*) : piece_(digits_, static_cast( numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0])) {} AlphaNum(unsigned long long x) // NOLINT(*) : piece_(digits_, static_cast( numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0])) {} AlphaNum(float f) // NOLINT(runtime/explicit) : piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {} AlphaNum(double f) // NOLINT(runtime/explicit) : piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {} template AlphaNum( // NOLINT(runtime/explicit) const strings_internal::AlphaNumBuffer& buf ABSL_ATTRIBUTE_LIFETIME_BOUND) : piece_(&buf.data[0], buf.size) {} AlphaNum(absl::Nullable c_str // NOLINT(runtime/explicit) ABSL_ATTRIBUTE_LIFETIME_BOUND) : piece_(NullSafeStringView(c_str)) {} AlphaNum(absl::string_view pc // NOLINT(runtime/explicit) ABSL_ATTRIBUTE_LIFETIME_BOUND) : piece_(pc) {} template ::value>::type> AlphaNum( // NOLINT(runtime/explicit) const T& v ABSL_ATTRIBUTE_LIFETIME_BOUND, strings_internal::StringifySink&& sink ABSL_ATTRIBUTE_LIFETIME_BOUND = {}) : piece_(strings_internal::ExtractStringification(sink, v)) {} template AlphaNum( // NOLINT(runtime/explicit) const std::basic_string, Allocator>& str ABSL_ATTRIBUTE_LIFETIME_BOUND) : piece_(str) {} // Use string literals ":" instead of character literals ':'. AlphaNum(char c) = delete; // NOLINT(runtime/explicit) AlphaNum(const AlphaNum&) = delete; AlphaNum& operator=(const AlphaNum&) = delete; absl::string_view::size_type size() const { return piece_.size(); } absl::Nullable data() const { return piece_.data(); } absl::string_view Piece() const { return piece_; } // Match unscoped enums. Use integral promotion so that a `char`-backed // enum becomes a wider integral type AlphaNum will accept. template {} && std::is_convertible{} && !HasAbslStringify::value>::type> AlphaNum(T e) // NOLINT(runtime/explicit) : AlphaNum(+e) {} // This overload matches scoped enums. We must explicitly cast to the // underlying type, but use integral promotion for the same reason as above. template {} && !std::is_convertible{} && !HasAbslStringify::value, char*>::type = nullptr> AlphaNum(T e) // NOLINT(runtime/explicit) : AlphaNum(+static_cast::type>(e)) {} // vector::reference and const_reference require special help to // convert to `AlphaNum` because it requires two user defined conversions. template < typename T, typename std::enable_if< std::is_class::value && (std::is_same::reference>::value || std::is_same::const_reference>::value)>::type* = nullptr> AlphaNum(T e) : AlphaNum(static_cast(e)) {} // NOLINT(runtime/explicit) private: absl::string_view piece_; char digits_[numbers_internal::kFastToBufferSize]; }; // ----------------------------------------------------------------------------- // StrCat() // ----------------------------------------------------------------------------- // // Merges given strings or numbers, using no delimiter(s), returning the merged // result as a string. // // `StrCat()` is designed to be the fastest possible way to construct a string // out of a mix of raw C strings, string_views, strings, bool values, // and numeric values. // // Don't use `StrCat()` for user-visible strings. The localization process // works poorly on strings built up out of fragments. // // For clarity and performance, don't use `StrCat()` when appending to a // string. Use `StrAppend()` instead. In particular, avoid using any of these // (anti-)patterns: // // str.append(StrCat(...)) // str += StrCat(...) // str = StrCat(str, ...) // // The last case is the worst, with a potential to change a loop // from a linear time operation with O(1) dynamic allocations into a // quadratic time operation with O(n) dynamic allocations. // // See `StrAppend()` below for more information. namespace strings_internal { // Do not call directly - this is not part of the public API. std::string CatPieces(std::initializer_list pieces); void AppendPieces(absl::Nonnull dest, std::initializer_list pieces); template std::string IntegerToString(Integer i) { // Any integer (signed/unsigned) up to 64 bits can be formatted into a buffer // with 22 bytes (including NULL at the end). constexpr size_t kMaxDigits10 = 22; std::string result; strings_internal::STLStringResizeUninitialized(&result, kMaxDigits10); char* start = &result[0]; // note: this can be optimized to not write last zero. char* end = numbers_internal::FastIntToBuffer(i, start); auto size = static_cast(end - start); assert((size < result.size()) && "StrCat(Integer) does not fit into kMaxDigits10"); result.erase(size); return result; } template std::string FloatToString(Float f) { std::string result; strings_internal::STLStringResizeUninitialized( &result, numbers_internal::kSixDigitsToBufferSize); char* start = &result[0]; result.erase(numbers_internal::SixDigitsToBuffer(f, start)); return result; } // `SingleArgStrCat` overloads take built-in `int`, `long` and `long long` types // (signed / unsigned) to avoid ambiguity on the call side. If we used int32_t // and int64_t, then at least one of the three (`int` / `long` / `long long`) // would have been ambiguous when passed to `SingleArgStrCat`. inline std::string SingleArgStrCat(int x) { return IntegerToString(x); } inline std::string SingleArgStrCat(unsigned int x) { return IntegerToString(x); } // NOLINTNEXTLINE inline std::string SingleArgStrCat(long x) { return IntegerToString(x); } // NOLINTNEXTLINE inline std::string SingleArgStrCat(unsigned long x) { return IntegerToString(x); } // NOLINTNEXTLINE inline std::string SingleArgStrCat(long long x) { return IntegerToString(x); } // NOLINTNEXTLINE inline std::string SingleArgStrCat(unsigned long long x) { return IntegerToString(x); } inline std::string SingleArgStrCat(float x) { return FloatToString(x); } inline std::string SingleArgStrCat(double x) { return FloatToString(x); } // As of September 2023, the SingleArgStrCat() optimization is only enabled for // libc++. The reasons for this are: // 1) The SSO size for libc++ is 23, while libstdc++ and MSSTL have an SSO size // of 15. Since IntegerToString unconditionally resizes the string to 22 bytes, // this causes both libstdc++ and MSSTL to allocate. // 2) strings_internal::STLStringResizeUninitialized() only has an // implementation that avoids initialization when using libc++. This isn't as // relevant as (1), and the cost should be benchmarked if (1) ever changes on // libstc++ or MSSTL. #ifdef _LIBCPP_VERSION #define ABSL_INTERNAL_STRCAT_ENABLE_FAST_CASE true #else #define ABSL_INTERNAL_STRCAT_ENABLE_FAST_CASE false #endif template {} && !std::is_same{}>> using EnableIfFastCase = T; #undef ABSL_INTERNAL_STRCAT_ENABLE_FAST_CASE } // namespace strings_internal ABSL_MUST_USE_RESULT inline std::string StrCat() { return std::string(); } template ABSL_MUST_USE_RESULT inline std::string StrCat( strings_internal::EnableIfFastCase a) { return strings_internal::SingleArgStrCat(a); } ABSL_MUST_USE_RESULT inline std::string StrCat(const AlphaNum& a) { return std::string(a.data(), a.size()); } ABSL_MUST_USE_RESULT std::string StrCat(const AlphaNum& a, const AlphaNum& b); ABSL_MUST_USE_RESULT std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c); ABSL_MUST_USE_RESULT std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c, const AlphaNum& d); // Support 5 or more arguments template ABSL_MUST_USE_RESULT inline std::string StrCat( const AlphaNum& a, const AlphaNum& b, const AlphaNum& c, const AlphaNum& d, const AlphaNum& e, const AV&... args) { return strings_internal::CatPieces( {a.Piece(), b.Piece(), c.Piece(), d.Piece(), e.Piece(), static_cast(args).Piece()...}); } // ----------------------------------------------------------------------------- // StrAppend() // ----------------------------------------------------------------------------- // // Appends a string or set of strings to an existing string, in a similar // fashion to `StrCat()`. // // WARNING: `StrAppend(&str, a, b, c, ...)` requires that none of the // a, b, c, parameters be a reference into str. For speed, `StrAppend()` does // not try to check each of its input arguments to be sure that they are not // a subset of the string being appended to. That is, while this will work: // // std::string s = "foo"; // s += s; // // This output is undefined: // // std::string s = "foo"; // StrAppend(&s, s); // // This output is undefined as well, since `absl::string_view` does not own its // data: // // std::string s = "foobar"; // absl::string_view p = s; // StrAppend(&s, p); inline void StrAppend(absl::Nonnull) {} void StrAppend(absl::Nonnull dest, const AlphaNum& a); void StrAppend(absl::Nonnull dest, const AlphaNum& a, const AlphaNum& b); void StrAppend(absl::Nonnull dest, const AlphaNum& a, const AlphaNum& b, const AlphaNum& c); void StrAppend(absl::Nonnull dest, const AlphaNum& a, const AlphaNum& b, const AlphaNum& c, const AlphaNum& d); // Support 5 or more arguments template inline void StrAppend(absl::Nonnull dest, const AlphaNum& a, const AlphaNum& b, const AlphaNum& c, const AlphaNum& d, const AlphaNum& e, const AV&... args) { strings_internal::AppendPieces( dest, {a.Piece(), b.Piece(), c.Piece(), d.Piece(), e.Piece(), static_cast(args).Piece()...}); } // Helper function for the future StrCat default floating-point format, %.6g // This is fast. inline strings_internal::AlphaNumBuffer< numbers_internal::kSixDigitsToBufferSize> SixDigits(double d) { strings_internal::AlphaNumBuffer result; result.size = numbers_internal::SixDigitsToBuffer(d, &result.data[0]); return result; } ABSL_NAMESPACE_END } // namespace absl #endif // ABSL_STRINGS_STR_CAT_H_