|
| 1 | +From c5e8f02c903696a213fc4b710f6740ccd1f07f4e Mon Sep 17 00:00:00 2001 |
| 2 | +From: Alexander Grund < [email protected]> |
| 3 | +Date: Sun, 3 Dec 2023 20:06:27 +0100 |
| 4 | +Subject: [PATCH] Reimplement `string_set` as `any_string` |
| 5 | + |
| 6 | +Use a better name for a type-erased string implemented using |
| 7 | +`dynamic_cast` instead of storing&comparing the `typeid` of the char |
| 8 | +type used. |
| 9 | +This is a workaround for missing typeinfo for `char8_t` on e.g. libc++ |
| 10 | +on FreeBSD 13.1. |
| 11 | +It also simplifies memory management size calc/copy implementation. |
| 12 | +--- |
| 13 | + boost/locale/detail/any_string.hpp | 71 ++++++++++++++++++++++ |
| 14 | + boost/locale/formatting.hpp | 51 ++-------------- |
| 15 | + libs/locale/src/boost/locale/shared/formatting.cpp | 45 -------------- |
| 16 | + libs/locale/test/test_ios_info.cpp | 64 +++++++++++++++---- |
| 17 | + 4 files changed, 127 insertions(+), 104 deletions(-) |
| 18 | + create mode 100644 boost/locale/detail/any_string.hpp |
| 19 | + |
| 20 | +diff --git a/boost/locale/detail/any_string.hpp b/boost/locale/detail/any_string.hpp |
| 21 | +new file mode 100644 |
| 22 | +index 00000000..c0cc7ffb |
| 23 | +--- /dev/null |
| 24 | ++++ b/boost/locale/detail/any_string.hpp |
| 25 | +@@ -0,0 +1,71 @@ |
| 26 | ++// |
| 27 | ++// Copyright (c) 2023 Alexander Grund |
| 28 | ++// |
| 29 | ++// Distributed under the Boost Software License, Version 1.0. |
| 30 | ++// https://www.boost.org/LICENSE_1_0.txt |
| 31 | ++ |
| 32 | ++#ifndef BOOST_LOCALE_DETAIL_ANY_STRING_HPP_INCLUDED |
| 33 | ++#define BOOST_LOCALE_DETAIL_ANY_STRING_HPP_INCLUDED |
| 34 | ++ |
| 35 | ++#include <boost/locale/config.hpp> |
| 36 | ++#include <boost/assert.hpp> |
| 37 | ++#include <boost/utility/string_view.hpp> |
| 38 | ++#include <memory> |
| 39 | ++#include <stdexcept> |
| 40 | ++#include <string> |
| 41 | ++ |
| 42 | ++/// \cond INTERNAL |
| 43 | ++namespace boost { namespace locale { namespace detail { |
| 44 | ++ /// Type-erased std::basic_string |
| 45 | ++ class any_string { |
| 46 | ++ struct BOOST_SYMBOL_VISIBLE base { |
| 47 | ++ virtual ~base() = default; |
| 48 | ++ virtual base* clone() const = 0; |
| 49 | ++ |
| 50 | ++ protected: |
| 51 | ++ base() = default; |
| 52 | ++ base(const base&) = default; |
| 53 | ++ base(base&&) = delete; |
| 54 | ++ base& operator=(const base&) = default; |
| 55 | ++ base& operator=(base&&) = delete; |
| 56 | ++ }; |
| 57 | ++ template<typename Char> |
| 58 | ++ struct BOOST_SYMBOL_VISIBLE impl : base { |
| 59 | ++ explicit impl(const boost::basic_string_view<Char> value) : s(value) {} |
| 60 | ++ impl* clone() const override { return new impl(*this); } |
| 61 | ++ std::basic_string<Char> s; |
| 62 | ++ }; |
| 63 | ++ |
| 64 | ++ std::unique_ptr<const base> s_; |
| 65 | ++ |
| 66 | ++ public: |
| 67 | ++ any_string() = default; |
| 68 | ++ any_string(const any_string& other) : s_(other.s_ ? other.s_->clone() : nullptr) {} |
| 69 | ++ any_string(any_string&&) = default; |
| 70 | ++ any_string& operator=(any_string other) // Covers the copy and move assignment |
| 71 | ++ { |
| 72 | ++ s_.swap(other.s_); |
| 73 | ++ return *this; |
| 74 | ++ } |
| 75 | ++ |
| 76 | ++ template<typename Char> |
| 77 | ++ void set(const boost::basic_string_view<Char> s) |
| 78 | ++ { |
| 79 | ++ BOOST_ASSERT(!s.empty()); |
| 80 | ++ s_.reset(new impl<Char>(s)); |
| 81 | ++ } |
| 82 | ++ |
| 83 | ++ template<typename Char> |
| 84 | ++ std::basic_string<Char> get() const |
| 85 | ++ { |
| 86 | ++ if(!s_) |
| 87 | ++ throw std::bad_cast(); |
| 88 | ++ return dynamic_cast<const impl<Char>&>(*s_).s; |
| 89 | ++ } |
| 90 | ++ }; |
| 91 | ++ |
| 92 | ++}}} // namespace boost::locale::detail |
| 93 | ++ |
| 94 | ++/// \endcond |
| 95 | ++ |
| 96 | ++#endif |
| 97 | +diff --git a/boost/locale/formatting.hpp b/boost/locale/formatting.hpp |
| 98 | +index d14e6f69..e3c8619e 100644 |
| 99 | +--- a/boost/locale/formatting.hpp |
| 100 | ++++ b/boost/locale/formatting.hpp |
| 101 | +@@ -8,15 +8,13 @@ |
| 102 | + #ifndef BOOST_LOCALE_FORMATTING_HPP_INCLUDED |
| 103 | + #define BOOST_LOCALE_FORMATTING_HPP_INCLUDED |
| 104 | + |
| 105 | ++#include <boost/locale/detail/any_string.hpp> |
| 106 | + #include <boost/locale/time_zone.hpp> |
| 107 | +-#include <boost/assert.hpp> |
| 108 | +-#include <boost/utility/string_view.hpp> |
| 109 | + #include <cstdint> |
| 110 | + #include <cstring> |
| 111 | + #include <istream> |
| 112 | + #include <ostream> |
| 113 | + #include <string> |
| 114 | +-#include <typeinfo> |
| 115 | + |
| 116 | + #ifdef BOOST_MSVC |
| 117 | + # pragma warning(push) |
| 118 | +@@ -130,13 +128,13 @@ namespace boost { namespace locale { |
| 119 | + template<typename CharType> |
| 120 | + void date_time_pattern(const std::basic_string<CharType>& str) |
| 121 | + { |
| 122 | +- date_time_pattern_set().set<CharType>(str); |
| 123 | ++ datetime_.set<CharType>(str); |
| 124 | + } |
| 125 | + /// Get date/time pattern (strftime like) |
| 126 | + template<typename CharType> |
| 127 | + std::basic_string<CharType> date_time_pattern() const |
| 128 | + { |
| 129 | +- return date_time_pattern_set().get<CharType>(); |
| 130 | ++ return datetime_.get<CharType>(); |
| 131 | + } |
| 132 | + |
| 133 | + /// \cond INTERNAL |
| 134 | +@@ -144,51 +142,10 @@ namespace boost { namespace locale { |
| 135 | + /// \endcond |
| 136 | + |
| 137 | + private: |
| 138 | +- class string_set; |
| 139 | +- |
| 140 | +- const string_set& date_time_pattern_set() const; |
| 141 | +- string_set& date_time_pattern_set(); |
| 142 | +- |
| 143 | +- class BOOST_LOCALE_DECL string_set { |
| 144 | +- public: |
| 145 | +- string_set(); |
| 146 | +- ~string_set(); |
| 147 | +- string_set(const string_set& other); |
| 148 | +- string_set& operator=(string_set other); |
| 149 | +- void swap(string_set& other); |
| 150 | +- |
| 151 | +- template<typename Char> |
| 152 | +- void set(const boost::basic_string_view<Char> s) |
| 153 | +- { |
| 154 | +- BOOST_ASSERT(!s.empty()); |
| 155 | +- delete[] ptr; |
| 156 | +- ptr = nullptr; |
| 157 | +- type = &typeid(Char); |
| 158 | +- size = sizeof(Char) * s.size(); |
| 159 | +- ptr = size ? new char[size] : nullptr; |
| 160 | +- memcpy(ptr, s.data(), size); |
| 161 | +- } |
| 162 | +- |
| 163 | +- template<typename Char> |
| 164 | +- std::basic_string<Char> get() const |
| 165 | +- { |
| 166 | +- if(type == nullptr || *type != typeid(Char)) |
| 167 | +- throw std::bad_cast(); |
| 168 | +- std::basic_string<Char> result(size / sizeof(Char), Char(0)); |
| 169 | +- memcpy(&result.front(), ptr, size); |
| 170 | +- return result; |
| 171 | +- } |
| 172 | +- |
| 173 | +- private: |
| 174 | +- const std::type_info* type; |
| 175 | +- size_t size; |
| 176 | +- char* ptr; |
| 177 | +- }; |
| 178 | +- |
| 179 | + uint64_t flags_; |
| 180 | + int domain_id_; |
| 181 | + std::string time_zone_; |
| 182 | +- string_set datetime_; |
| 183 | ++ detail::any_string datetime_; |
| 184 | + }; |
| 185 | + |
| 186 | + /// \brief This namespace includes all manipulators that can be used on IO streams |
| 187 | +diff --git a/libs/locale/src/boost/locale/shared/formatting.cpp b/libs/locale/src/boost/locale/shared/formatting.cpp |
| 188 | +index 489d1fd5..457ba782 100644 |
| 189 | +--- a/libs/locale/src/boost/locale/shared/formatting.cpp |
| 190 | ++++ b/libs/locale/src/boost/locale/shared/formatting.cpp |
| 191 | +@@ -7,43 +7,8 @@ |
| 192 | + #include <boost/locale/date_time.hpp> |
| 193 | + #include <boost/locale/formatting.hpp> |
| 194 | + #include "boost/locale/shared/ios_prop.hpp" |
| 195 | +-#include <algorithm> |
| 196 | +-#include <typeinfo> |
| 197 | + |
| 198 | + namespace boost { namespace locale { |
| 199 | +- |
| 200 | +- ios_info::string_set::string_set() : type(nullptr), size(0), ptr(nullptr) {} |
| 201 | +- ios_info::string_set::~string_set() |
| 202 | +- { |
| 203 | +- delete[] ptr; |
| 204 | +- } |
| 205 | +- ios_info::string_set::string_set(const string_set& other) |
| 206 | +- { |
| 207 | +- if(other.ptr != nullptr) { |
| 208 | +- ptr = new char[other.size]; |
| 209 | +- size = other.size; |
| 210 | +- type = other.type; |
| 211 | +- memcpy(ptr, other.ptr, size); |
| 212 | +- } else { |
| 213 | +- ptr = nullptr; |
| 214 | +- size = 0; |
| 215 | +- type = nullptr; |
| 216 | +- } |
| 217 | +- } |
| 218 | +- |
| 219 | +- void ios_info::string_set::swap(string_set& other) |
| 220 | +- { |
| 221 | +- std::swap(type, other.type); |
| 222 | +- std::swap(size, other.size); |
| 223 | +- std::swap(ptr, other.ptr); |
| 224 | +- } |
| 225 | +- |
| 226 | +- ios_info::string_set& ios_info::string_set::operator=(string_set other) |
| 227 | +- { |
| 228 | +- swap(other); |
| 229 | +- return *this; |
| 230 | +- } |
| 231 | +- |
| 232 | + ios_info::ios_info() : flags_(0), domain_id_(0), time_zone_(time_zone::global()) {} |
| 233 | + |
| 234 | + ios_info::~ios_info() = default; |
| 235 | +@@ -105,16 +70,6 @@ namespace boost { namespace locale { |
| 236 | + return time_zone_; |
| 237 | + } |
| 238 | + |
| 239 | +- const ios_info::string_set& ios_info::date_time_pattern_set() const |
| 240 | +- { |
| 241 | +- return datetime_; |
| 242 | +- } |
| 243 | +- |
| 244 | +- ios_info::string_set& ios_info::date_time_pattern_set() |
| 245 | +- { |
| 246 | +- return datetime_; |
| 247 | +- } |
| 248 | +- |
| 249 | + ios_info& ios_info::get(std::ios_base& ios) |
| 250 | + { |
| 251 | + return impl::ios_prop<ios_info>::get(ios); |
| 252 | +diff --git a/libs/locale/test/test_ios_info.cpp b/libs/locale/test/test_ios_info.cpp |
| 253 | +index 9b63aaed..79179a8f 100644 |
| 254 | +--- a/libs/locale/test/test_ios_info.cpp |
| 255 | ++++ b/libs/locale/test/test_ios_info.cpp |
| 256 | +@@ -105,18 +105,6 @@ void test_member_methods() |
| 257 | + |
| 258 | + info.date_time_pattern(std::string("Pattern")); |
| 259 | + TEST_EQ(info.date_time_pattern<char>(), "Pattern"); |
| 260 | +- |
| 261 | +- info.date_time_pattern(ascii_to<wchar_t>("WChar Pattern")); |
| 262 | +- TEST_EQ(info.date_time_pattern<wchar_t>(), ascii_to<wchar_t>("WChar Pattern")); |
| 263 | +- TEST_THROWS(info.date_time_pattern<char>(), std::bad_cast); |
| 264 | +- |
| 265 | +- info.date_time_pattern(ascii_to<char16_t>("Char16 Pattern")); |
| 266 | +- TEST_THROWS(info.date_time_pattern<wchar_t>(), std::bad_cast); |
| 267 | +- TEST_EQ(info.date_time_pattern<char16_t>(), ascii_to<char16_t>("Char16 Pattern")); |
| 268 | +- |
| 269 | +- info.date_time_pattern(ascii_to<char32_t>("Char32 Pattern")); |
| 270 | +- TEST_THROWS(info.date_time_pattern<char16_t>(), std::bad_cast); |
| 271 | +- TEST_EQ(info.date_time_pattern<char32_t>(), ascii_to<char32_t>("Char32 Pattern")); |
| 272 | + } |
| 273 | + } |
| 274 | + |
| 275 | +@@ -212,8 +200,60 @@ void test_manipulators() |
| 276 | + TEST_EQ(info2.date_time_pattern<wchar_t>(), L"My TZ"); |
| 277 | + } |
| 278 | + |
| 279 | ++void test_any_string() |
| 280 | ++{ |
| 281 | ++ boost::locale::detail::any_string s; |
| 282 | ++ TEST_THROWS(s.get<char>(), std::bad_cast); |
| 283 | ++ TEST_THROWS(s.get<wchar_t>(), std::bad_cast); |
| 284 | ++ |
| 285 | ++ s.set<char>("Char Pattern"); |
| 286 | ++ TEST_EQ(s.get<char>(), "Char Pattern"); |
| 287 | ++ TEST_THROWS(s.get<wchar_t>(), std::bad_cast); |
| 288 | ++ |
| 289 | ++ s.set<wchar_t>(ascii_to<wchar_t>("WChar Pattern")); |
| 290 | ++ TEST_EQ(s.get<wchar_t>(), ascii_to<wchar_t>("WChar Pattern")); |
| 291 | ++ TEST_THROWS(s.get<char>(), std::bad_cast); |
| 292 | ++ |
| 293 | ++ s.set<char16_t>(ascii_to<char16_t>("Char16 Pattern")); |
| 294 | ++ TEST_EQ(s.get<char16_t>(), ascii_to<char16_t>("Char16 Pattern")); |
| 295 | ++ TEST_THROWS(s.get<char>(), std::bad_cast); |
| 296 | ++ |
| 297 | ++ s.set<char32_t>(ascii_to<char32_t>("Char32 Pattern")); |
| 298 | ++ TEST_EQ(s.get<char32_t>(), ascii_to<char32_t>("Char32 Pattern")); |
| 299 | ++ TEST_THROWS(s.get<char16_t>(), std::bad_cast); |
| 300 | ++ |
| 301 | ++#ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
| 302 | ++ s.set<char8_t>(ascii_to<char8_t>("Char8 Pattern")); |
| 303 | ++ TEST_EQ(s.get<char8_t>(), ascii_to<char8_t>("Char8 Pattern")); |
| 304 | ++ TEST_THROWS(s.get<char32_t>(), std::bad_cast); |
| 305 | ++#endif |
| 306 | ++ |
| 307 | ++ boost::locale::detail::any_string s1, s2, empty; |
| 308 | ++ s1.set<char>("Char"); |
| 309 | ++ s2.set<wchar_t>(ascii_to<wchar_t>("WChar")); |
| 310 | ++ // Copy ctor |
| 311 | ++ boost::locale::detail::any_string s3(s1); |
| 312 | ++ TEST_EQ(s3.get<char>(), "Char"); |
| 313 | ++ TEST_EQ(s1.get<char>(), "Char"); |
| 314 | ++ // Ensure deep copy |
| 315 | ++ s3.set<char>("Foo"); |
| 316 | ++ TEST_EQ(s3.get<char>(), "Foo"); |
| 317 | ++ TEST_EQ(s1.get<char>(), "Char"); |
| 318 | ++ // Copy assign |
| 319 | ++ s3 = s2; |
| 320 | ++ TEST_EQ(s3.get<wchar_t>(), ascii_to<wchar_t>("WChar")); |
| 321 | ++ TEST_EQ(s2.get<wchar_t>(), ascii_to<wchar_t>("WChar")); |
| 322 | ++ // Move assign |
| 323 | ++ s3 = std::move(s1); |
| 324 | ++ TEST_EQ(s3.get<char>(), "Char"); |
| 325 | ++ // From empty |
| 326 | ++ s3 = empty; |
| 327 | ++ TEST_THROWS(s3.get<char>(), std::bad_cast); |
| 328 | ++} |
| 329 | ++ |
| 330 | + void test_main(int /*argc*/, char** /*argv*/) |
| 331 | + { |
| 332 | ++ test_any_string(); |
| 333 | + test_member_methods(); |
| 334 | + test_manipulators(); |
| 335 | + } |
0 commit comments