diff options
author | 2021-02-07 22:15:44 -0800 | |
---|---|---|
committer | 2021-06-03 16:29:35 -0700 | |
commit | 4188ffdbcf068eced5b64f77e96da96ee64de7e2 (patch) | |
tree | f60e6f1f28083b0a1749f005ba95411d05555f3a | |
parent | dda9bbadaf7aedf4cc5938ab131147a7bd05ce63 (diff) |
FTL: Add to_chars
Wrap std::to_chars for a slightly faster alternative to std::to_string
with optional radix.
The upcoming ftl/{enum.h,flags.h} imported from IF will use the binary/
hex options, while SF will stringify display IDs for trace labels using
the non-allocating version.
Bug: 185536303
Test: ftl_test
Change-Id: I587622763303d2baeb844e52eff55ce8f0bcbf1e
-rw-r--r-- | include/ftl/string.h | 101 | ||||
-rw-r--r-- | libs/ftl/Android.bp | 1 | ||||
-rw-r--r-- | libs/ftl/string_test.cpp | 187 |
3 files changed, 289 insertions, 0 deletions
diff --git a/include/ftl/string.h b/include/ftl/string.h new file mode 100644 index 0000000000..2d96b06a2f --- /dev/null +++ b/include/ftl/string.h @@ -0,0 +1,101 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +#pragma once + +#include <cassert> +#include <charconv> +#include <limits> +#include <string> +#include <string_view> +#include <type_traits> + +namespace android::ftl { + +enum class Radix { kBin = 2, kDec = 10, kHex = 16 }; + +template <typename T> +struct to_chars_length { + static_assert(std::is_integral_v<T>); + // Maximum binary digits, plus minus sign and radix prefix. + static constexpr std::size_t value = std::numeric_limits<std::make_unsigned_t<T>>::digits + 3; +}; + +template <typename T> +constexpr std::size_t to_chars_length_v = to_chars_length<T>::value; + +template <typename T = std::int64_t> +using to_chars_buffer_t = char[to_chars_length_v<T>]; + +// Lightweight (not allocating nor sprintf-based) alternative to std::to_string for integers, with +// optional radix. See also ftl::to_string below. +// +// ftl::to_chars_buffer_t<> buffer; +// +// assert(ftl::to_chars(buffer, 123u) == "123"); +// assert(ftl::to_chars(buffer, -42, ftl::Radix::kBin) == "-0b101010"); +// assert(ftl::to_chars(buffer, 0xcafe, ftl::Radix::kHex) == "0xcafe"); +// assert(ftl::to_chars(buffer, '*', ftl::Radix::kHex) == "0x2a"); +// +template <typename T, std::size_t N> +std::string_view to_chars(char (&buffer)[N], T v, Radix radix = Radix::kDec) { + static_assert(N >= to_chars_length_v<T>); + + auto begin = buffer + 2; + const auto [end, err] = std::to_chars(begin, buffer + N, v, static_cast<int>(radix)); + assert(err == std::errc()); + + if (radix == Radix::kDec) { + // TODO: Replace with {begin, end} in C++20. + return {begin, static_cast<std::size_t>(end - begin)}; + } + + const auto prefix = radix == Radix::kBin ? 'b' : 'x'; + if constexpr (std::is_unsigned_v<T>) { + buffer[0] = '0'; + buffer[1] = prefix; + } else { + if (*begin == '-') { + *buffer = '-'; + } else { + --begin; + } + + *begin-- = prefix; + *begin = '0'; + } + + // TODO: Replace with {buffer, end} in C++20. + return {buffer, static_cast<std::size_t>(end - buffer)}; +} + +// Lightweight (not sprintf-based) alternative to std::to_string for integers, with optional radix. +// +// assert(ftl::to_string(123u) == "123"); +// assert(ftl::to_string(-42, ftl::Radix::kBin) == "-0b101010"); +// assert(ftl::to_string(0xcafe, ftl::Radix::kHex) == "0xcafe"); +// assert(ftl::to_string('*', ftl::Radix::kHex) == "0x2a"); +// +template <typename T> +inline std::string to_string(T v, Radix radix = Radix::kDec) { + to_chars_buffer_t<T> buffer; + return std::string(to_chars(buffer, v, radix)); +} + +std::string to_string(bool) = delete; +std::string to_string(bool, Radix) = delete; + +} // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 97626bec7d..4226e32ee7 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -18,6 +18,7 @@ cc_test { "small_map_test.cpp", "small_vector_test.cpp", "static_vector_test.cpp", + "string_test.cpp", ], cflags: [ "-Wall", diff --git a/libs/ftl/string_test.cpp b/libs/ftl/string_test.cpp new file mode 100644 index 0000000000..f3d85c8319 --- /dev/null +++ b/libs/ftl/string_test.cpp @@ -0,0 +1,187 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +#include <ftl/string.h> +#include <gtest/gtest.h> + +#include <algorithm> +#include <cstdint> +#include <iterator> +#include <limits> +#include <sstream> +#include <type_traits> + +namespace android::test { + +// Keep in sync with example usage in header file. +TEST(String, ToChars) { + ftl::to_chars_buffer_t<> buffer; + + EXPECT_EQ(ftl::to_chars(buffer, 123u), "123"); + EXPECT_EQ(ftl::to_chars(buffer, -42, ftl::Radix::kBin), "-0b101010"); + EXPECT_EQ(ftl::to_chars(buffer, 0xcafe, ftl::Radix::kHex), "0xcafe"); + EXPECT_EQ(ftl::to_chars(buffer, '*', ftl::Radix::kHex), "0x2a"); +} + +namespace { + +template <typename F, typename T> +void ToCharsTest() { + constexpr auto kRadix = F::kRadix; + + using Limits = std::numeric_limits<T>; + constexpr auto kMin = Limits::min(); + constexpr auto kMax = Limits::max(); + constexpr auto kNeg = static_cast<T>(-42); + constexpr auto kPos = static_cast<T>(123); + + ftl::to_chars_buffer_t<T> buffer; + + EXPECT_EQ(ftl::to_chars(buffer, kMin, kRadix), F{}(kMin)); + EXPECT_EQ(ftl::to_chars(buffer, kMax, kRadix), F{}(kMax)); + EXPECT_EQ(ftl::to_chars(buffer, kNeg, kRadix), F{}(kNeg)); + EXPECT_EQ(ftl::to_chars(buffer, kPos, kRadix), F{}(kPos)); +} + +template <typename...> +struct Types {}; + +template <typename F, typename Types> +struct ToCharsTests; + +template <typename F, typename T, typename... Ts> +struct ToCharsTests<F, Types<T, Ts...>> { + static void test() { + ToCharsTest<F, T>(); + ToCharsTests<F, Types<Ts...>>::test(); + } +}; + +template <typename F> +struct ToCharsTests<F, Types<>> { + static void test() {} +}; + +template <typename T, typename U = std::make_unsigned_t<T>> +U to_unsigned(std::ostream& stream, T v) { + if (std::is_same_v<T, U>) return v; + + if (v < 0) { + stream << '-'; + return std::numeric_limits<U>::max() - static_cast<U>(v) + 1; + } else { + return static_cast<U>(v); + } +} + +struct Bin { + static constexpr auto kRadix = ftl::Radix::kBin; + + template <typename T> + std::string operator()(T v) const { + std::ostringstream stream; + auto u = to_unsigned(stream, v); + stream << "0b"; + + if (u == 0) { + stream << 0; + } else { + std::ostringstream digits; + do { + digits << (u & 1); + } while (u >>= 1); + + const auto str = digits.str(); + std::copy(str.rbegin(), str.rend(), std::ostream_iterator<char>(stream)); + } + + return stream.str(); + } +}; + +struct Dec { + static constexpr auto kRadix = ftl::Radix::kDec; + + template <typename T> + std::string operator()(T v) const { + return std::to_string(v); + } +}; + +struct Hex { + static constexpr auto kRadix = ftl::Radix::kHex; + + template <typename T> + std::string operator()(T v) const { + std::ostringstream stream; + const auto u = to_unsigned(stream, v); + stream << "0x" << std::hex << std::nouppercase; + stream << (sizeof(T) == 1 ? static_cast<unsigned>(u) : u); + return stream.str(); + } +}; + +using IntegerTypes = + Types<char, unsigned char, signed char, std::uint8_t, std::uint16_t, std::uint32_t, + std::uint64_t, std::int8_t, std::int16_t, std::int32_t, std::int64_t>; + +} // namespace + +TEST(String, ToCharsBin) { + ToCharsTests<Bin, IntegerTypes>::test(); + + { + const std::uint8_t x = 0b1111'1111; + ftl::to_chars_buffer_t<decltype(x)> buffer; + EXPECT_EQ(ftl::to_chars(buffer, x, ftl::Radix::kBin), "0b11111111"); + } + { + const std::int16_t x = -0b1000'0000'0000'0000; + ftl::to_chars_buffer_t<decltype(x)> buffer; + EXPECT_EQ(ftl::to_chars(buffer, x, ftl::Radix::kBin), "-0b1000000000000000"); + } +} + +TEST(String, ToCharsDec) { + ToCharsTests<Dec, IntegerTypes>::test(); + + { + const std::uint32_t x = UINT32_MAX; + ftl::to_chars_buffer_t<decltype(x)> buffer; + EXPECT_EQ(ftl::to_chars(buffer, x), "4294967295"); + } + { + const std::int32_t x = INT32_MIN; + ftl::to_chars_buffer_t<decltype(x)> buffer; + EXPECT_EQ(ftl::to_chars(buffer, x), "-2147483648"); + } +} + +TEST(String, ToCharsHex) { + ToCharsTests<Hex, IntegerTypes>::test(); + + { + const std::uint16_t x = 0xfade; + ftl::to_chars_buffer_t<decltype(x)> buffer; + EXPECT_EQ(ftl::to_chars(buffer, x, ftl::Radix::kHex), "0xfade"); + } + { + ftl::to_chars_buffer_t<> buffer; + EXPECT_EQ(ftl::to_chars(buffer, INT64_MIN, ftl::Radix::kHex), "-0x8000000000000000"); + } +} + +} // namespace android::test |