diff --git a/CMakeLists.txt b/CMakeLists.txt index d3d5fba..6d1a410 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ set(HEADER_FILES conversion/binaryconversionprivate.h conversion/conversionexception.h conversion/stringconversion.h + conversion/stringbuilder.h conversion/types.h conversion/widen.h io/ansiescapecodes.h diff --git a/README.md b/README.md index 1e8ed6a..cc5530c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ The library utilizes: - split, join, find and replace - conversion from number to string and vice verca - encoding/decoding base-64 + - building string without multiple heap allocations (string builder) * IO - reading/writing primitive data types of various sizes (little-endian and big-endian) - reading/writing terminated strings and size-prefixed strings diff --git a/conversion/stringbuilder.h b/conversion/stringbuilder.h new file mode 100644 index 0000000..c7af011 --- /dev/null +++ b/conversion/stringbuilder.h @@ -0,0 +1,184 @@ +#ifndef CONVERSION_UTILITIES_STRINGBUILDER_H +#define CONVERSION_UTILITIES_STRINGBUILDER_H + +#include "../misc/traits.h" + +#include +#include + +namespace ConversionUtilities +{ + +/// \cond +namespace Helper { + +template > > +std::size_t computeTupleElementSize(const StringType *str) +{ + return str->size(); +} + +template +std::size_t computeTupleElementSize(const StringType &str) +{ + return str.size(); +} + +template +std::size_t computeTupleElementSize(const CharType *str) +{ + return std::char_traits::length(str); +} + +template +void append(StringType &target, const StringType *str) +{ + target.append(*str); +} + +template +void append(StringType &target, const StringType &str) +{ + target.append(str); +} + +template +void append(StringType &target, const CharType *str) +{ + target.append(str); +} + +template +struct TupleToString { + static std::size_t precomputeSize(const Tuple &tuple) + { + return TupleToString::precomputeSize(tuple) + computeTupleElementSize(std::get(tuple)); + } + + static void append(const Tuple &tuple, StringType &str) + { + TupleToString::append(tuple, str); + Helper::append(str, std::get(tuple)); + } +}; + +template +struct TupleToString { + static std::size_t precomputeSize(const Tuple &tuple) + { + return computeTupleElementSize(std::get<0>(tuple)); + } + + static void append(const Tuple &tuple, StringType &str) + { + Helper::append(str, std::get<0>(tuple)); + } +}; + +template +class StringTuple : public std::tuple +{ +public: + StringTuple(Elements&&... elements) : + std::tuple(elements...) + {} + + +}; + +template +constexpr auto makeStringTuple(Elements&&... elements) -> decltype(StringTuple(elements...)) +{ + return StringTuple(elements...); +} + +} +/// \endcond + +/*! + * \brief Concatenates all strings hold by the specified \a tuple. + */ +template +StringType tupleToString(const std::tuple &tuple) +{ + StringType res; + res.reserve(Helper::TupleToString::precomputeSize(tuple)); + Helper::TupleToString::append(tuple, res); + return res; +} + +/*! + * \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3. + */ +template +constexpr auto operator %(const Tuple &lhs, const std::string &rhs) -> decltype(std::tuple_cat(lhs, std::make_tuple(&rhs))) +{ + return std::tuple_cat(lhs, std::make_tuple(&rhs)); +} + +/*! + * \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3. + */ +template +constexpr auto operator %(const Tuple &lhs, const char *rhs) -> decltype(std::tuple_cat(lhs, std::make_tuple(rhs))) +{ + return std::tuple_cat(lhs, std::make_tuple(rhs)); +} + +/*! + * \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3. + */ +constexpr auto operator %(const std::string &lhs, const std::string &rhs) -> decltype(std::make_tuple(&lhs, &rhs)) +{ + return std::make_tuple(&lhs, &rhs); +} + +/*! + * \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3. + */ +constexpr auto operator %(const char *lhs, const std::string &rhs) -> decltype(std::make_tuple(lhs, &rhs)) +{ + return std::make_tuple(lhs, &rhs); +} + +/*! + * \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3. + */ +constexpr auto operator %(const std::string &lhs, const char *rhs) -> decltype(std::make_tuple(&lhs, rhs)) +{ + return std::make_tuple(&lhs, rhs); +} + +/*! + * \brief Allows construction of final string from previously constructed string-tuple and trailing string via +-operator. + * + * This is meant to be used for fast string building without multiple heap allocation, eg. + * + * ``` + * printVelocity("velocity: " % numberToString(velocityExample) % " km/h (" % numberToString(velocityExample / 3.6) + " m/s)")); + * ``` + */ +template +inline std::string operator +(const Tuple &lhs, const std::string &rhs) +{ + return tupleToString(std::tuple_cat(lhs, std::make_tuple(&rhs))); +} + +/*! + * \brief Allows construction of final string from previously constructed string-tuple and trailing string via +-operator. + * + * This is meant to be used for fast string building without multiple heap allocation, eg. + * + * ``` + * printVelocity("velocity: " % numberToString(velocityExample) % " km/h (" % numberToString(velocityExample / 3.6) + " m/s)")); + * ``` + */ +template +inline std::string operator +(const Tuple &lhs, const char *rhs) +{ + return tupleToString(std::tuple_cat(lhs, std::make_tuple(rhs))); +} + +} + +#endif // CONVERSION_UTILITIES_STRINGBUILDER_H diff --git a/tests/conversiontests.cpp b/tests/conversiontests.cpp index 7bd9df7..d77ffd3 100644 --- a/tests/conversiontests.cpp +++ b/tests/conversiontests.cpp @@ -1,5 +1,6 @@ #include "../conversion/binaryconversion.h" #include "../conversion/stringconversion.h" +#include "../conversion/stringbuilder.h" #include "../tests/testutils.h" #include @@ -27,6 +28,7 @@ class ConversionTests : public TestFixture CPPUNIT_TEST(testSwapOrderFunctions); CPPUNIT_TEST(testStringEncodingConversions); CPPUNIT_TEST(testStringConversions); + CPPUNIT_TEST(testStringBuilder); CPPUNIT_TEST_SUITE_END(); public: @@ -40,6 +42,7 @@ public: void testSwapOrderFunctions(); void testStringEncodingConversions(); void testStringConversions(); + void testStringBuilder(); private: template @@ -295,3 +298,18 @@ void ConversionTests::testStringConversions() CPPUNIT_ASSERT(decodedBase64Data.first[i] == originalBase64Data[i]); } } + +string functionTakingString(const string &str) +{ + return str; +} + +void ConversionTests::testStringBuilder() +{ + const tuple tuple("string1", "string2", "string3"); + CPPUNIT_ASSERT_EQUAL(string("string1string2string3"), tupleToString(tuple)); + CPPUNIT_ASSERT_EQUAL(string("foobarfoo2bar2"), tupleToString(string("foo") % "bar" % string("foo2") % "bar2")); + CPPUNIT_ASSERT_EQUAL(string("123456"), functionTakingString("12" % string("34") + "56")); + constexpr double velocityExample = 27.0; + CPPUNIT_ASSERT_EQUAL(string("velocity: 27 km/h (7.5 m/s)"), functionTakingString("velocity: " % numberToString(velocityExample) % " km/h (" % numberToString(velocityExample / 3.6) + " m/s)")); +} diff --git a/tests/testutils.h b/tests/testutils.h index 7770b6d..2d958bf 100644 --- a/tests/testutils.h +++ b/tests/testutils.h @@ -148,7 +148,8 @@ template AsHexNumber asHexNumber(const T &value) * \brief Asserts successful execution of application via TestApplication::execApp(). Output is stored in stdout and stderr. * \remarks Requires cppunit. */ -# define TESTUTILS_ASSERT_EXEC(args) CPPUNIT_ASSERT_EQUAL(0, execApp(args, stdout, stderr)) +# define TESTUTILS_ASSERT_EXEC(args) \ + CPPUNIT_ASSERT_EQUAL(0, execApp(args, stdout, stderr)) #endif }