diff options
Diffstat (limited to 'include/rtl/stringconcat.hxx')
-rw-r--r-- | include/rtl/stringconcat.hxx | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/include/rtl/stringconcat.hxx b/include/rtl/stringconcat.hxx new file mode 100644 index 000000000..f5c9a4d33 --- /dev/null +++ b/include/rtl/stringconcat.hxx @@ -0,0 +1,553 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_RTL_STRINGCONCAT_HXX +#define INCLUDED_RTL_STRINGCONCAT_HXX + +// This file is only included from LIBO_INTERNAL_ONLY + +#include "rtl/stringutils.hxx" +#include "rtl/string.h" +#include "rtl/ustring.h" + +#include <cstddef> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> + +#include <string.h> + +#if defined RTL_STRING_UNITTEST_CONCAT +extern bool rtl_string_unittest_invalid_concat; +#endif + +#ifdef RTL_STRING_UNITTEST +#define rtl rtlunittest +#endif +namespace rtl +{ +#ifdef RTL_STRING_UNITTEST +#undef rtl +#endif + +/* +Implementation of efficient string concatenation. + +The whole system is built around two basic template classes: +- ToStringHelper< T > - for each T it can give the length of the resulting string representation and can write + this string representation to a buffer +- O(U)StringConcat< T1, T2 > - operator+ now, instead of creating O(U)String object, returns only this helper object, + that keeps a reference to both operator+ operands; only when converted to O(U)String it will actually create + the resulting string object using ToStringHelper, creating directly the resulting object without any string + intermediate objects +As all the code is inline methods, it allows for extensive optimization and will usually result in very effective code +(even surpassing strlen/strcat and equalling handwritten), while allowing for very easy and intuitive syntax. +*/ + +/** +@internal + +Helper class for converting a given type to a string representation. +*/ +template< typename T > +struct ToStringHelper + { + /// Return length of the string representation of the given object (if not known exactly, it needs to be the maximum). + static std::size_t length( const T& ); + /// Add 8-bit representation of the given object to the given buffer and return position right after the added data. + static char* addData( char* buffer, const T& ) SAL_RETURNS_NONNULL; + /// Add Unicode representation of the given object to the given buffer and return position right after the added data. + static sal_Unicode* addData( sal_Unicode* buffer, const T& ) SAL_RETURNS_NONNULL; + /// If true, T can be used in concatenation resulting in OString. + static const bool allowOStringConcat = false; + /// If true, T can be used in concatenation resulting in OUString. + static const bool allowOUStringConcat = false; + }; + +inline +char* addDataHelper( char* buffer, const char* data, std::size_t length ) + { + memcpy( buffer, data, length ); + return buffer + length; + } + +inline +sal_Unicode* addDataHelper( sal_Unicode* buffer, const sal_Unicode* data, std::size_t length ) + { + memcpy( buffer, data, length * sizeof( sal_Unicode )); + return buffer + length; + } + +inline +sal_Unicode* addDataLiteral( sal_Unicode* buffer, const char* data, std::size_t length ) + { + for( std::size_t i = 0; i != length; ++i ) + *buffer++ = *data++; + return buffer; + } + +inline +char* addDataCString( char* buffer, const char* str ) + { + while( *str != '\0' ) + *buffer++ = *str++; + return buffer; + } + +inline +sal_Unicode* addDataUString( sal_Unicode* buffer, const sal_Unicode* str ) + { + while( *str != '\0' ) + *buffer++ = *str++; + return buffer; + } + +template<> +struct ToStringHelper< const char* > + { + static std::size_t length( const char* str ) { + return str ? strlen( str ) : 0; + } + static char* addData( char* buffer, const char* str ) { + return str ? addDataCString( buffer, str ) : buffer; + } + static const bool allowOStringConcat = true; + static const bool allowOUStringConcat = false; + }; + +template<> +struct ToStringHelper< char* > : public ToStringHelper< const char* > {}; + +template< std::size_t N > +struct ToStringHelper< char[ N ] > + { + static std::size_t length( const char str[ N ] ) { + return strlen( str ); + } + static char* addData( char* buffer, const char str[ N ] ) { return addDataCString( buffer, str ); } + static const bool allowOStringConcat = true; + static const bool allowOUStringConcat = false; + }; + +template< std::size_t N > +struct ToStringHelper< const char[ N ] > + { + static std::size_t length( const char str[ N ] ) { (void)str; assert( strlen( str ) == N - 1 ); return N - 1; } + static char* addData( char* buffer, const char str[ N ] ) { return addDataHelper( buffer, str, N - 1 ); } + static sal_Unicode* addData( sal_Unicode* buffer, const char str[ N ] ) { return addDataLiteral( buffer, str, N - 1 ); } + static const bool allowOStringConcat = true; + static const bool allowOUStringConcat = true; + }; + +template<> +struct ToStringHelper<OStringChar> + { + static std::size_t length(OStringChar) { return 1; } + static char* addData(char* buffer, OStringChar data) + { return addDataHelper(buffer, &data.c, 1); } + static bool const allowOStringConcat = true; + static bool const allowOUStringConcat = false; + }; + +template<> +struct ToStringHelper< const sal_Unicode* > + { + static std::size_t length( const sal_Unicode* str ) { + return str ? std::char_traits<char16_t>::length( str ) : 0; + } + static sal_Unicode* addData( sal_Unicode* buffer, const sal_Unicode* str ) { + return str ? addDataUString( buffer, str ) : buffer; + } + static const bool allowOStringConcat = false; + static const bool allowOUStringConcat = true; + }; + +template<> +struct ToStringHelper< sal_Unicode* > : public ToStringHelper< const sal_Unicode* > {}; + +template<std::size_t N> +struct ToStringHelper<sal_Unicode[ N ]> + { + static std::size_t length( const sal_Unicode str[ N ] ) { + return std::char_traits<char16_t>::length( str ); + } + static sal_Unicode * addData(sal_Unicode * buffer, sal_Unicode const str[N]) + { return addDataHelper(buffer, str, N - 1); } + static bool const allowOStringConcat = false; + static bool const allowOUStringConcat = true; + }; + +template<std::size_t N> +struct ToStringHelper<sal_Unicode const[N]> + { + static std::size_t length( const sal_Unicode str[ N ] ) { (void)str; assert( std::char_traits<char16_t>::length( str ) == N - 1 ); return N - 1; } + static sal_Unicode * addData(sal_Unicode * buffer, sal_Unicode const str[N]) + { return addDataHelper(buffer, str, N - 1); } + static bool const allowOStringConcat = false; + static bool const allowOUStringConcat = true; + }; + +template<> +struct ToStringHelper<OUStringChar_> + { + static std::size_t length(OUStringChar_) { return 1; } + static sal_Unicode * addData(sal_Unicode * buffer, OUStringChar_ literal) + { return addDataHelper(buffer, &literal.c, 1); } + static bool const allowOStringConcat = false; + static bool const allowOUStringConcat = true; + }; + +/** +@internal + +Objects returned by operator+, instead of OString. These objects (possibly recursively) keep a representation of the whole +concatenation operation. + +If you get a build error related to this class, you most probably need to explicitly convert the result of a string +concatenation to OString. +*/ +template< typename T1, typename T2 > +struct OStringConcat + { + public: + OStringConcat( const T1& left_, const T2& right_ ) : left( left_ ), right( right_ ) {} + std::size_t length() const { return ToStringHelper< T1 >::length( left ) + ToStringHelper< T2 >::length( right ); } + char* addData( char* buffer ) const SAL_RETURNS_NONNULL { return ToStringHelper< T2 >::addData( ToStringHelper< T1 >::addData( buffer, left ), right ); } + // NOTE here could be functions that would forward to the "real" temporary OString. Note however that e.g. getStr() + // is not so simple, as the OString temporary must live long enough (i.e. can't be created here in a function, a wrapper + // temporary object containing it must be returned instead). + private: + const T1& left; + const T2& right; + }; + +/** +@internal + +Objects returned by operator+, instead of OUString. These objects (possibly recursively) keep a representation of the whole +concatenation operation. + +If you get a build error related to this class, you most probably need to explicitly convert the result of a string +concatenation to OUString. +*/ +template< typename T1, typename T2 > +struct OUStringConcat + { + public: + OUStringConcat( const T1& left_, const T2& right_ ) : left( left_ ), right( right_ ) {} + std::size_t length() const { return ToStringHelper< T1 >::length( left ) + ToStringHelper< T2 >::length( right ); } + sal_Unicode* addData( sal_Unicode* buffer ) const SAL_RETURNS_NONNULL { return ToStringHelper< T2 >::addData( ToStringHelper< T1 >::addData( buffer, left ), right ); } + private: + const T1& left; + const T2& right; + }; + +template< typename T1, typename T2 > +struct ToStringHelper< OStringConcat< T1, T2 > > + { + static std::size_t length( const OStringConcat< T1, T2 >& c ) { return c.length(); } + static char* addData( char* buffer, const OStringConcat< T1, T2 >& c ) SAL_RETURNS_NONNULL { return c.addData( buffer ); } + static const bool allowOStringConcat = ToStringHelper< T1 >::allowOStringConcat && ToStringHelper< T2 >::allowOStringConcat; + static const bool allowOUStringConcat = false; + }; + +template< typename T1, typename T2 > +struct ToStringHelper< OUStringConcat< T1, T2 > > + { + static std::size_t length( const OUStringConcat< T1, T2 >& c ) { return c.length(); } + static sal_Unicode* addData( sal_Unicode* buffer, const OUStringConcat< T1, T2 >& c ) SAL_RETURNS_NONNULL { return c.addData( buffer ); } + static const bool allowOStringConcat = false; + static const bool allowOUStringConcat = ToStringHelper< T1 >::allowOUStringConcat && ToStringHelper< T2 >::allowOUStringConcat; + }; + +template< typename T1, typename T2 > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T1 >::allowOStringConcat && ToStringHelper< T2 >::allowOStringConcat, OStringConcat< T1, T2 > > operator+( const T1& left, const T2& right ) + { + return OStringConcat< T1, T2 >( left, right ); + } + +// char[N] and const char[N] need to be done explicitly, otherwise the compiler likes to treat them the same way for some reason +template< typename T, std::size_t N > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T >::allowOStringConcat, OStringConcat< T, const char[ N ] > > operator+( const T& left, const char (&right)[ N ] ) + { + return OStringConcat< T, const char[ N ] >( left, right ); + } + +template< typename T, std::size_t N > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T >::allowOStringConcat, OStringConcat< const char[ N ], T > > operator+( const char (&left)[ N ], const T& right ) + { + return OStringConcat< const char[ N ], T >( left, right ); + } + +template< typename T, std::size_t N > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T >::allowOStringConcat, OStringConcat< T, char[ N ] > > operator+( const T& left, char (&right)[ N ] ) + { + return OStringConcat< T, char[ N ] >( left, right ); + } + +template< typename T, std::size_t N > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T >::allowOStringConcat, OStringConcat< char[ N ], T > > operator+( char (&left)[ N ], const T& right ) + { + return OStringConcat< char[ N ], T >( left, right ); + } + +template< typename T1, typename T2 > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T1 >::allowOUStringConcat && ToStringHelper< T2 >::allowOUStringConcat, OUStringConcat< T1, T2 > > operator+( const T1& left, const T2& right ) + { + return OUStringConcat< T1, T2 >( left, right ); + } + +template< typename T1, typename T2 > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T1 >::allowOUStringConcat && ToStringHelper< T2 >::allowOUStringConcat && libreoffice_internal::ConstCharArrayDetector< T1, void >::ok, OUStringConcat< T1, T2 > > operator+( T1& left, const T2& right ) + { + return OUStringConcat< T1, T2 >( left, right ); + } + +template< typename T1, typename T2 > +[[nodiscard]] +inline +typename std::enable_if_t< ToStringHelper< T1 >::allowOUStringConcat && ToStringHelper< T2 >::allowOUStringConcat && libreoffice_internal::ConstCharArrayDetector< T2, void >::ok, OUStringConcat< T1, T2 > > operator+( const T1& left, T2& right ) + { + return OUStringConcat< T1, T2 >( left, right ); + } + +#ifdef RTL_STRING_UNITTEST_CONCAT +// Special overload to catch the remaining invalid combinations. The helper struct must +// be used to make this operator+ overload a worse choice than all the existing overloads above. +struct StringConcatInvalid + { + template< typename T > + StringConcatInvalid( const T& ) {} + }; +template< typename T > +inline +int operator+( const StringConcatInvalid&, const T& ) + { + rtl_string_unittest_invalid_concat = true; + return 0; // doesn't matter + } +#endif + +/** + @internal + +Objects returned by OString::number(), instead of OString. These objects keep a representation of the number() operation. + +If you get a build error related to this class, you most probably need to explicitly convert the result of calling +OString::number() to OString. +*/ +template< typename T > +struct OStringNumber; + +template <typename Number, std::size_t nBufSize> struct OStringNumberBase +{ + using number_t = Number; + // OString::number(value).getStr() is very common (writing xml code, ...), + // so implement that one also here, to avoid having to explicitly to convert + // to OString in all such places + const char * getStr() const SAL_RETURNS_NONNULL { return buf; } + OStringNumber<number_t>&& toAsciiUpperCase() + { + rtl_str_toAsciiUpperCase_WithLength(buf, length); + return std::move(*static_cast<OStringNumber<number_t>*>(this)); + } + operator std::string_view() const { return std::string_view(buf, length); } + char buf[nBufSize]; + sal_Int32 length; +}; + +template<> +struct OStringNumber< int > + : public OStringNumberBase<int, RTL_STR_MAX_VALUEOFINT32> +{ + OStringNumber(number_t i, sal_Int16 radix) { length = rtl_str_valueOfInt32(buf, i, radix); } +}; + +template<> +struct OStringNumber< long long > + : public OStringNumberBase<long long, RTL_STR_MAX_VALUEOFINT64> +{ + OStringNumber(number_t i, sal_Int16 radix) { length = rtl_str_valueOfInt64(buf, i, radix); } +}; + +template<> +struct OStringNumber< unsigned long long > + : public OStringNumberBase<unsigned long long, RTL_STR_MAX_VALUEOFUINT64> +{ + OStringNumber(number_t i, sal_Int16 radix) { length = rtl_str_valueOfUInt64(buf, i, radix); } +}; + +template<> +struct OStringNumber< float > + : public OStringNumberBase<float, RTL_STR_MAX_VALUEOFFLOAT> +{ + OStringNumber(number_t f) { length = rtl_str_valueOfFloat(buf, f); } +}; + +template<> +struct OStringNumber< double > + : public OStringNumberBase<double, RTL_STR_MAX_VALUEOFDOUBLE> +{ + OStringNumber(number_t d) { length = rtl_str_valueOfDouble(buf, d); } +}; + +template< typename T > +struct ToStringHelper< OStringNumber< T > > + { + static std::size_t length( const OStringNumber< T >& n ) { return n.length; } + static char* addData( char* buffer, const OStringNumber< T >& n ) SAL_RETURNS_NONNULL { return addDataHelper( buffer, n.buf, n.length ); } + static const bool allowOStringConcat = true; + static const bool allowOUStringConcat = false; + }; + + +/** + @internal + +Objects returned by OUString::number(), instead of OUString. These objects keep a representation of the number() operation. + +If you get a build error related to this class, you most probably need to explicitly convert the result of calling +OUString::number() to OUString. +*/ +template< typename T > +struct OUStringNumber; + +template <typename Number, std::size_t nBufSize> struct OUStringNumberBase +{ + using number_t = Number; + OUStringNumber<number_t>&& toAsciiUpperCase() + { + rtl_ustr_toAsciiUpperCase_WithLength(buf, length); + return std::move(*static_cast<OUStringNumber<number_t>*>(this)); + } + operator std::u16string_view() const { return std::u16string_view(buf, length); } + sal_Unicode buf[nBufSize]; + sal_Int32 length; +}; + +template<> +struct OUStringNumber< int > + : public OUStringNumberBase<int, RTL_USTR_MAX_VALUEOFINT32> +{ + OUStringNumber(number_t i, sal_Int16 radix) { length = rtl_ustr_valueOfInt32(buf, i, radix); } +}; + +template<> +struct OUStringNumber< long long > + : public OUStringNumberBase<long long, RTL_USTR_MAX_VALUEOFINT64> +{ + OUStringNumber(number_t i, sal_Int16 radix) { length = rtl_ustr_valueOfInt64(buf, i, radix); } +}; + +template<> +struct OUStringNumber< unsigned long long > + : public OUStringNumberBase<unsigned long long, RTL_USTR_MAX_VALUEOFUINT64> +{ + OUStringNumber(number_t i, sal_Int16 radix) { length = rtl_ustr_valueOfUInt64(buf, i, radix); } +}; + +template<> +struct OUStringNumber< float > + : public OUStringNumberBase<float, RTL_USTR_MAX_VALUEOFFLOAT> +{ + OUStringNumber(number_t f) { length = rtl_ustr_valueOfFloat(buf, f); } +}; + +template<> +struct OUStringNumber< double > + : public OUStringNumberBase<double, RTL_USTR_MAX_VALUEOFDOUBLE> +{ + OUStringNumber(number_t d) { length = rtl_ustr_valueOfDouble(buf, d); } +}; + +template< typename T > +struct ToStringHelper< OUStringNumber< T > > + { + static std::size_t length( const OUStringNumber< T >& n ) { return n.length; } + static sal_Unicode* addData( sal_Unicode* buffer, const OUStringNumber< T >& n ) SAL_RETURNS_NONNULL { return addDataHelper( buffer, n.buf, n.length ); } + static const bool allowOStringConcat = false; + static const bool allowOUStringConcat = true; + }; + +// Abstractions over null-terminated char and sal_Unicode strings that sometimes are needed in +// concatenations of multiple such raw strings, as in +// +// char const * s1, s2; +// OString s = OStringView(s1) + s2; +// +// (Providing specializations of ToStringHelper<std::string_view> and +// ToStringHelper<std::u16string_view> would look dubious, as it would give meaning to expressions +// like +// +// std::string_view(s1) + s2 +// +// that do not involve any user-defined types.) + +class OStringView { +public: + explicit OStringView(char const * s): view_(s) {} + explicit OStringView(char const * s, size_t len): view_(s, len) {} + + std::size_t length() const { return view_.length(); } + + char const * data() const { return view_.data(); } + +private: + std::string_view view_; +}; + +template<> +struct ToStringHelper< OStringView > + { + static std::size_t length( const OStringView& v ) { return v.length(); } + static char* addData( char* buffer, const OStringView& v ) SAL_RETURNS_NONNULL { return addDataHelper( buffer, v.data(), v.length() ); } + static const bool allowOStringConcat = true; + static const bool allowOUStringConcat = false; + }; + +class OUStringView { +public: + explicit OUStringView(sal_Unicode const * s): view_(s) {} + explicit OUStringView(sal_Unicode const * s, size_t len): view_(s, len) {} + + std::size_t length() const { return view_.length(); } + + sal_Unicode const * data() const { return view_.data(); } + +private: + std::u16string_view view_; +}; + +template<> +struct ToStringHelper< OUStringView > + { + static std::size_t length( const OUStringView& v ) { return v.length(); } + static sal_Unicode* addData( sal_Unicode* buffer, const OUStringView& v ) SAL_RETURNS_NONNULL { return addDataHelper( buffer, v.data(), v.length() ); } + static const bool allowOStringConcat = false; + static const bool allowOUStringConcat = true; + }; + +} // namespace + +#endif |