diff --git a/runtime/ClangTidySuppressions.txt b/runtime/ClangTidySuppressions.txt index feead8b..664229a 100644 --- a/runtime/ClangTidySuppressions.txt +++ b/runtime/ClangTidySuppressions.txt @@ -9,6 +9,14 @@ cppcoreguidelines-avoid-c-arrays:src/zserio/Span.h:174 # This is necessary for low level implementation of Span to mimic standard C++20 'std::span' abstraction. cppcoreguidelines-pro-bounds-array-to-pointer-decay:src/zserio/Span.h:113 +# The following is filtered out because bounds are checked naturally by implementation. Therefore method 'at' +# would only bring the performance drop. +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:44 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:45 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:50 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:55 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:56 + # This is necessary for low level implementation of Span to mimic standard C++20 'std::span' abstraction. google-explicit-constructor:src/zserio/Span.h:112 google-explicit-constructor:src/zserio/Span.h:125 diff --git a/runtime/src/CMakeLists.txt b/runtime/src/CMakeLists.txt index d9d0892..8748da6 100644 --- a/runtime/src/CMakeLists.txt +++ b/runtime/src/CMakeLists.txt @@ -21,7 +21,10 @@ set(ZSERIO_CPP17_RUNTIME_LIB_SRCS zserio/BuiltInOperators.cpp zserio/BuiltInOperators.h zserio/CppRuntimeVersion.h + zserio/RebindAlloc.h zserio/Span.h + zserio/String.h + zserio/StringConvertUtil.h zserio/Types.h ) diff --git a/runtime/src/zserio/RebindAlloc.h b/runtime/src/zserio/RebindAlloc.h new file mode 100644 index 0000000..8ca1bc6 --- /dev/null +++ b/runtime/src/zserio/RebindAlloc.h @@ -0,0 +1,14 @@ +#ifndef ZSERIO_REBIND_ALLOC_H_INC +#define ZSERIO_REBIND_ALLOC_H_INC + +#include + +namespace zserio +{ + +template +using RebindAlloc = typename std::allocator_traits::template rebind_alloc; + +} // namespace zserio + +#endif // ZSERIO_REBIND_ALLOC_H_INC diff --git a/runtime/src/zserio/String.h b/runtime/src/zserio/String.h new file mode 100644 index 0000000..d811139 --- /dev/null +++ b/runtime/src/zserio/String.h @@ -0,0 +1,21 @@ +#ifndef ZSERIO_STRING_H_INC +#define ZSERIO_STRING_H_INC + +#include + +#include "zserio/RebindAlloc.h" + +namespace zserio +{ + +/** + * Typedef to std::string provided for convenience - using std::allocator. + * + * Automatically rebinds the given allocator. + */ +template > +using string = std::basic_string, RebindAlloc>; + +} // namespace zserio + +#endif // ZSERIO_STRING_H_INC diff --git a/runtime/src/zserio/StringConvertUtil.h b/runtime/src/zserio/StringConvertUtil.h new file mode 100644 index 0000000..c7f81ef --- /dev/null +++ b/runtime/src/zserio/StringConvertUtil.h @@ -0,0 +1,213 @@ +#ifndef ZSERIO_STRING_CONVERT_UTIL_H_INC +#define ZSERIO_STRING_CONVERT_UTIL_H_INC + +#include +#include +#include +#include + +#include "zserio/String.h" + +namespace zserio +{ + +namespace detail +{ + +/** + * Converts integer value to string into the given buffer. + * + * The string is filled from backwards starting at the 24th byte. + * + * \return Beginning of the resulting string which is null-terminated. + */ +template ::value && !std::is_same::value, int>::type = 0> +const char* convertIntToString(std::array& buffer, T value, bool isNegative) +{ + static const std::array DIGITS_100_10 = { + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"}; + static const std::array DIGITS_1 = {"0123456789"}; + + auto bufferEnd = buffer.end(); + *(--bufferEnd) = '\0'; // always terminate with '\0' + + while (value >= 100) + { + const unsigned int index = static_cast((value % 100) * 2); + value /= 100; + *(--bufferEnd) = DIGITS_100_10[index + 1]; + *(--bufferEnd) = DIGITS_100_10[index]; + } + + if (value < 10) + { + *(--bufferEnd) = DIGITS_1[static_cast(value)]; + } + else + { + const unsigned int index = static_cast(value * 2); + *(--bufferEnd) = DIGITS_100_10[index + 1]; + *(--bufferEnd) = DIGITS_100_10[index]; + } + + if (isNegative) + { + *(--bufferEnd) = '-'; + } + + return &(*bufferEnd); +} + +} // namespace detail + +/** + * Converts unsigned integral value to string and writes the result to the given buffer. + * + * Note that the buffer is filled from behind. + * + * \param buffer Buffer to fill with the string representation of the given value. + * \param value Value to convert. + * + * \return Pointer to the beginning of the resulting string. + */ +template ::value, int>::type = 0> +const char* convertIntToString(std::array& buffer, T value) +{ + return detail::convertIntToString(buffer, value, false); +} + +/** + * Converts signed integral value to string and writes the result to the given buffer. + * + * Note that the buffer is filled from behind. + * + * \param buffer Buffer to fill with the string representation of the given value. + * \param value Value to convert. + * + * \return Pointer to the beginning of the resulting string. + */ +template ::value, int>::type = 0> +const char* convertIntToString(std::array& buffer, T value) +{ + using unsigned_type = typename std::make_unsigned::type; + unsigned_type absValue = static_cast(value); + const bool isNegative = value < 0; + if (isNegative) + { + absValue = static_cast(0 - absValue); + } + + return detail::convertIntToString(buffer, absValue, isNegative); +} + +/** + * Converts float to string and writes the result to the given buffer. + * + * Note that only five three digits after point are used and that the buffers are filled from behind. + * + * \param integerPartBuffer Buffer to fill with the string representation of the integer part. + * \param floatingPartBuffer Buffer to fill with the string representation of the floating part. + * \param value Value to convert. + * \param floatingPartString Reference where to fill pointer to the beginning of the floating part in string. + * \param integerPartString Reference where to fill pointer to the beginning of the integer part in string. + */ +inline void convertFloatToString(std::array& integerPartBuffer, + std::array& floatingPartBuffer, float value, const char*& integerPartString, + const char*& floatingPartString) +{ + if (value >= static_cast(std::numeric_limits::max())) + { + integerPartString = "+Inf"; + floatingPartString = nullptr; + } + else if (value <= static_cast(std::numeric_limits::min())) + { + integerPartString = "-Inf"; + floatingPartString = nullptr; + } + else + { + const int64_t integerPart = static_cast(value); + const int64_t floatingPart = + static_cast((value - static_cast(integerPart)) * 1e3F); // 3 digits + const int64_t floatingPartAbs = (floatingPart < 0) ? 0 - floatingPart : floatingPart; + integerPartString = convertIntToString(integerPartBuffer, integerPart); + floatingPartString = convertIntToString(floatingPartBuffer, floatingPartAbs); + } +} + +/** + * Converts bool value to boolalpha C-string ("true" or "false"). + * + * \param value Value to convert. + * + * \return C-string representation of the given bool value. + */ +inline const char* convertBoolToString(bool value) +{ + return value ? "true" : "false"; +} + +/** + * Converts an integral value to string using the given allocator. Defined for convenience. + * + * \param value Value to convert. + * \param allocator Allocator to use for the string allocation. + * + * \return String representation of the given integral value. + */ +template +string toString(T value, const ALLOC& allocator = ALLOC()) +{ + std::array buffer = {}; + return string(convertIntToString(buffer, value), allocator); +} + +/** + * Converts a boolean value to string using the given allocator. Defined for convenience. + * + * Note that in contrast to std::to_string, this behaves as STL streams with boolalpha flag and produces + * "true" and "false" strings. + * + * \param value Value to convert. + * \param allocator Allocator to use for the string allocation. + */ +template +string toString(bool value, const ALLOC& allocator = ALLOC()) +{ + return string(convertBoolToString(value), allocator); +} + +/** + * Converts a string view to string using the given allocator. Defined for convenience. + * + * \param value String view to convert. + * \param allocator Allocator to use for the string allocation. + */ +template +string toString(std::string_view value, const ALLOC& allocator = ALLOC()) +{ + return string(value, allocator); +} + +/** + * Converts an integral (or a boolean) value to string. Convenience wrapper to call without allocator. + * + * \param value Value to convert. + * + * \return String representation of the given value. + */ +template +string> toString(T value) +{ + return toString>(value); +} + +} // namespace zserio + +#endif // ifndef ZSERIO_STRING_CONVERT_UTIL_H_INC diff --git a/runtime/test/CMakeLists.txt b/runtime/test/CMakeLists.txt index 07155d6..f17feb5 100644 --- a/runtime/test/CMakeLists.txt +++ b/runtime/test/CMakeLists.txt @@ -14,6 +14,7 @@ gtest_add_library("${ZSERIO_CPP17_PROJECT_ROOT}/3rdparty/cpp/googletest") set(ZSERIO_CPP17_RUNTIME_TEST_SRCS zserio/BuiltInOperatorsTest.cpp zserio/SpanTest.cpp + zserio/StringConvertUtilTest.cpp ) add_executable(${PROJECT_NAME} ${ZSERIO_CPP17_RUNTIME_TEST_SRCS}) diff --git a/runtime/test/zserio/StringConvertUtilTest.cpp b/runtime/test/zserio/StringConvertUtilTest.cpp new file mode 100644 index 0000000..3af942b --- /dev/null +++ b/runtime/test/zserio/StringConvertUtilTest.cpp @@ -0,0 +1,152 @@ +#include +#include + +#include "gtest/gtest.h" +#include "zserio/StringConvertUtil.h" + +namespace zserio +{ + +using allocator_type = std::allocator; + +TEST(StringConvertUtilTest, convertInt8) +{ + using type = int8_t; + const type value = std::numeric_limits::min(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertUInt8) +{ + using type = uint8_t; + const type value = std::numeric_limits::max(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertInt16) +{ + using type = int16_t; + const type value = std::numeric_limits::min(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertUInt16) +{ + using type = uint16_t; + const type value = std::numeric_limits::max(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertInt32) +{ + using type = int32_t; + const type value = std::numeric_limits::min(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertUInt32) +{ + using type = uint32_t; + const type value = std::numeric_limits::max(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertInt64) +{ + using type = int64_t; + const type value = std::numeric_limits::min(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertUInt64) +{ + using type = uint64_t; + const type value = std::numeric_limits::max(); + std::array buffer = {}; + const char* valueInString = convertIntToString(buffer, value); + EXPECT_EQ(std::to_string(value), valueInString); + + EXPECT_EQ(std::to_string(value), toString(value)); +} + +TEST(StringConvertUtilTest, convertFloat) +{ + std::array integerBuffer = {}; + std::array floatingBuffer = {}; + const char* integerString = ""; + const char* floatingString = ""; + convertFloatToString(integerBuffer, floatingBuffer, 13579.2468F, integerString, floatingString); + EXPECT_EQ(std::string("13579"), integerString); + EXPECT_EQ(std::string("247"), floatingString); + + convertFloatToString(integerBuffer, floatingBuffer, -2468.123456789F, integerString, floatingString); + EXPECT_EQ(std::string("-2468"), integerString); + EXPECT_EQ(std::string("123"), floatingString); + + convertFloatToString(integerBuffer, floatingBuffer, 1.0F, integerString, floatingString); + EXPECT_EQ(std::string("1"), integerString); + EXPECT_EQ(std::string("0"), floatingString); + + convertFloatToString(integerBuffer, floatingBuffer, 1E10F, integerString, floatingString); + EXPECT_EQ(std::string("10000000000"), integerString); + EXPECT_EQ(std::string("0"), floatingString); + + convertFloatToString(integerBuffer, floatingBuffer, -1E10F, integerString, floatingString); + EXPECT_EQ(std::string("-10000000000"), integerString); + EXPECT_EQ(std::string("0"), floatingString); + + convertFloatToString(integerBuffer, floatingBuffer, 1E20F, integerString, floatingString); + EXPECT_EQ(std::string("+Inf"), integerString); + EXPECT_EQ(nullptr, floatingString); + + convertFloatToString(integerBuffer, floatingBuffer, -1E20F, integerString, floatingString); + EXPECT_EQ(std::string("-Inf"), integerString); + EXPECT_EQ(nullptr, floatingString); +} + +TEST(StringConvertUtilTest, convertBool) +{ + EXPECT_EQ(std::string("true"), convertBoolToString(true)); + EXPECT_EQ(std::string("false"), convertBoolToString(false)); + + EXPECT_EQ("true", toString(true)); + EXPECT_EQ("false", toString(false)); +} + +TEST(StringConvertUtilTest, convertStringView) +{ + EXPECT_EQ(std::string(), toString(std::string_view())); + EXPECT_EQ("test", toString(std::string_view("test"))); + const std::array buffer = {'t', 'e', 's', 't'}; + EXPECT_EQ("test", toString(std::string_view(buffer.data(), buffer.size()))); +} + +} // namespace zserio