diff --git a/docs/Doxyfile b/docs/Doxyfile
index 23ae796..150ed96 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = "simstr"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 1.0
+PROJECT_NUMBER = 1.2.4
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewers a
@@ -290,7 +290,8 @@ TAB_SIZE = 4
# with the commands \{ and \} for these it is advised to use the version @{ and
# @} or use a double escape (\\{ and \\})
-ALIASES =
+ALIASES = ru=\~russian \
+ en=\~english
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
diff --git a/docs/overview.md b/docs/overview.md
index a75182d..c13887d 100644
--- a/docs/overview.md
+++ b/docs/overview.md
@@ -1,30 +1,31 @@
-# Строки в С++
-(, что с вами не так?)
+# Strings in C++
+(, what's wrong with you?)
+
+[On Russian|По-русски](overview_ru.md)
-В ретроспективе 1991 года по истории C++ его создатель Бьярне Страуструп назвал отсутствие стандартного строкового типа
-(и некоторых других стандартных типов) в C++ 1.0 худшей ошибкой, которую он допустил при его разработке:
-"the absence of those led to everybody re-inventing the wheel and to an unnecessary diversity in the most fundamental classes"
-(«Их отсутствие привело к тому, что все заново изобретали велосипед, и к ненужному разнообразию в самых фундаментальных классах»).
+In a 1991 retrospective on the history of C++, its creator Bjarne Stroustrup called the lack of a standard string type
+(and some other standard types) in C++ 1.0 the worst mistake he made in its development:
+"Their absence led to everyone reinventing the wheel and to an unnecessary diversity in the most fundamental classes"
-## Что было и есть
-Во вступительной части я хочу немного описать, каково ныне состояние со строками в С++, как мы к нему докатились и почему оно таково.
-А также описать недостатки текущих реализаций, чтобы были понятны решения, которые я использую в своей библиотеке строк.
+## What was and is
+In the introductory part, I want to briefly describe the current state of strings in C++, how we got to it, and why it is so.
+I will also describe the shortcomings of current implementations so that the solutions I use in my string library are clear.
-Собственно, изначально как такового стандартного типа для строк в С++ не было.
-Для работы со строками использовался подход из C – строка есть указатель на массив байтов, оканчивающихся нулём.
-Недостатки таких строк — невозможно в строке использовать байт `0`, т. е. не подходит для бинарных данных,
-непонятна стратегия управления/владения ресурсами, ну и основной недостаток — длину строки приходится вычислять каждый раз,
-перебирая все её символы.
+Actually, initially there was no standard type for strings in C++.
+The approach from C was used to work with strings – a string is a pointer to an array of bytes ending in zero.
+The disadvantages of such strings are that it is impossible to use the byte `0` in the string, i.e. it is not suitable for binary data,
+the resource management/ownership strategy is unclear, and the main disadvantage is that the length of the string has to be calculated each time,
+iterating over all its characters.
-Откуда ноги растут у такого решения вполне понятно — со времён динозавров: как динозавры были большие, с маленьким мозгом и
-короткими ручками, так и компьютеры были большие, память у них была маленькая, а строки короткими. Сэкономить память на хранении длины строки было важнее, чем потерять время на повторный подсчет длины.
+The origin of this solution is quite clear – from the time of the dinosaurs: just as dinosaurs were large, with small brains and
+short arms, so computers were large, memory was small, and strings were short. Saving memory on storing the length of a string was more important than losing time on repeatedly calculating the length.
-Первые попытки стандартизировать строки как класс начались только в С++98 - std::string появился, как часть STL, и как
-многое из STL, крайне неоднозначно воспринимался программистами.
+The first attempts to standardize strings as a class began only in C++98 - std::string appeared as part of STL, and like
+much of STL, it was extremely ambiguously perceived by programmers.
-И первое, что приходит в голову при улучшении C-строк — надо хранить длину строки:
+And the first thing that comes to mind when improving C-strings is that you need to store the length of the string:
```cpp
struct simple_string {
const char* data;
@@ -32,92 +33,92 @@
};
```
-При наличии такой строки, уже множество алгоритмов значительно оптимизируются.
-Например, при сравнении двух строк на равенство мы можем даже не начинать сравнивать их символы, если длины строк не равны.
-Более того, этих данных абсолютно достаточно для всех методов, которые не модифицируют строку.
-Также заметим, что такой объект на современных 64-битных архитектурах прекрасно передается в функции по значению —
-оба его поля укладываются в регистры (ну, кроме windows), что облегчает работу оптимизатору компилятора.
+With such a string, many algorithms are significantly optimized.
+For example, when comparing two strings for equality, we may not even start comparing their characters if the lengths of the strings are not equal.
+Moreover, this data is absolutely sufficient for all methods that do not modify the string.
+Also note that such an object on modern 64-bit architectures is perfectly passed to functions by value –
+both of its fields fit into registers (well, except for Windows), which makes it easier for the compiler optimizer to work.
-Между тем, такое решение попало в стандарт только аж в С++17, в виде `std::string_view`.
-Видимо, только тогда до комитета смогли донести мысль, что строки строкам рознь, и использовать только один универсальный объект
-для строк — по меньшей мере может приводить к уменьшению производительности, а также нарушает принцип «не плати за то,
-чем не пользуешься». Почему же «строки строкам рознь» и почему нам мало одного типа для строки, рассмотрим как раз далее.
+Meanwhile, such a solution only made it into the standard in C++17, in the form of `std::string_view`.
+Apparently, only then could the committee be convinced that strings are different, and using only one universal object
+for strings – at the very least, can lead to a decrease in performance, and also violates the principle of "don't pay for what you
+don't use". Why are "strings different" and why is one type of string not enough for us, we will consider just below.
-### Ресурсы
-И следующий вопрос, возникающий со строками — это владение ресурсами.
-Практически каждый крупный фреймворк решал эту задачу самостоятельно, изобретая свои велосипеды.
-У нас есть `std::string`, в QT у нас `QString`, в MFC - `CString`, в ATL - `CAtlString`, свои строки есть в Folly,
-в общем, “тысячи их”, любой игровой движок начинают с того, чтобы написать свои строки.
+### Resources
+And the next question that arises with strings is resource ownership.
+Almost every major framework solved this problem on its own, inventing its own bicycles.
+We have `std::string`, in QT we have `QString`, in MFC - `CString`, in ATL - `CAtlString`, there are own strings in Folly,
+in general, "thousands of them", any game engine starts with writing its own strings.
-Многие из этих реализаций в аспекте управления ресурсами для улучшения производительности использовали подход
-**COW** – “Copy On Write”. При этом объект строки ссылался на некий разделяемый между несколькими объектами буфер с символами
-строки и счётчиком ссылок на этот буфер, что позволяло быстро создавать копию строки, а реально копировать символы только
-при её модификации.
+Many of these implementations in the aspect of resource management used the approach to improve performance
+**COW** – “Copy On Write”. In this case, the string object referred to a buffer shared between several objects with the characters
+of the string and a reference counter to this buffer, which allowed you to quickly create a copy of the string, and actually copy the characters only
+when it is modified.
-Но все они совпадали в одном — строка всегда предполагалась мутабельной, то есть что мы можем модифицировать символы в буфере строки.
+But they all coincided in one thing – the string was always assumed to be mutable, that is, we can modify the characters in the string buffer.
-### Мутабельность / иммутабельность
-Из-за этого подход **COW** умер к С++11: при каждой операции, могущей модифицировать символы строки приходилось проверять,
-не ссылаемся ли мы на разделяемый буфер и если да, то копировать символы в другой буфер.
-В многопоточной же среде потом ещё и проверять, не надо ли теперь освобождать старый буфер, и естественно всё это обмазавшись
-локами или атомиками, что тоже не бесплатно.
-Поэтому, начиная с С++11 `std::string` не использует **COW**, и каждое копирование объекта строки приводит и к копированию
-всех символов строки в другой буфер.
+### Mutability / immutability
+Because of this, the **COW** approach died by C++11: for each operation that could modify the characters of the string, it was necessary to check
+whether we are referring to a shared buffer, and if so, copy the characters to another buffer.
+In a multithreaded environment, you also need to check whether you now need to free the old buffer, and of course, all this is smeared with
+locks or atomics, which is also not free.
+Therefore, starting with C++11, `std::string` does not use **COW**, and each copying of a string object also leads to copying
+all the characters of the string to another buffer.
-Естественно, что каждый новый буфер требует аллокации памяти, что пытаются немного оптимизировать за счёт **SSO** –
-“Small String Optimization”, когда объект строки содержит внутри себя небольшой буфер и символы коротких строк
-располагаются прямо в нём.
-Но это уже зависит от реализации: в одних библиотеках помещают в объект строки до 15 байт, в некоторых до 23.
-Однако эта оптимизация тоже палка о двух концах, и может в различных реализациях усложнить перемещение строки - если она хранит
-указатель на свой внутренний буфер, его придётся корректировать.
+Naturally, each new buffer requires memory allocation, which they are trying to slightly optimize through **SSO** –
+“Small String Optimization”, when the string object contains a small buffer inside itself and the characters of short strings
+are located directly in it.
+But this already depends on the implementation: in some libraries they place up to 15 bytes in the string object, in some up to 23.
+However, this optimization is also a double-edged sword, and in various implementations it can complicate the movement of a string - if it stores
+a pointer to its internal buffer, it will have to be adjusted.
-А без COW мутабельность строк приводит к тому, что любая инициализация объекта строки приводит к копированию байтов.
-Посмотрим такой код:
+And without COW, the mutability of strings leads to the fact that any initialization of a string object leads to copying bytes.
+Let's look at this code:
```cpp
- const char* text1 = "Hello, World"; // ничего не стоит
- std::string_view text2 = "Hello, World"; // Ничего не стоит, вычисляет длину строки при компиляции
- std::string text3 = "Hello, World"; // В рантайме каждый раз копирует символы строки
+ const char* text1 = "Hello, World"; // costs nothing
+ std::string_view text2 = "Hello, World"; // Costs nothing, calculates the length of the string at compile time
+ std::string text3 = "Hello, World"; // Copies the characters of the string every time at runtime
```
-(Удостоверится в правдивости комментариев можно на https://godbolt.org/z/51oKGWT5T )
+(You can verify the truth of the comments at https://godbolt.org/z/51oKGWT5T )
-Но если нам дальше по коду не нужно никак модифицировать строку, мы зря платим за аллокацию, копирование символов,
-а также за деструктор строки. То есть хотелось бы иметь как минимум два варианта строк — мутабельные и иммутабельные,
-чтобы явно дать понять компилятору, что мы не собираемся модифицировать строку.
-Или банальный пример — мы парсим какой-то входящий буфер данных, нам нужно проверить, равен ли некий кусок буфера строке
-”hello” на «чистом С++», т. е. без всяких memcmp и strcmp. До появления string_view приходилось делать примерно так:
+But if we don’t need to modify the string in any way further in the code, we are wasting money on allocation, copying characters,
+as well as on the string destructor. That is, I would like to have at least two versions of strings – mutable and immutable,
+to explicitly make it clear to the compiler that we are not going to modify the string.
+Or a banal example – we are parsing some incoming data buffer, we need to check whether a certain piece of the buffer is equal to the string
+"hello" in "pure C++", i.e. without any memcmp and strcmp. Before the advent of string_view, it had to be done something like this:
```cpp
bool is_part_buffer_equal_hello(const char* data, int start, int end) {
return std::string(data + start, end - start) == "hello";
}
```
-Тут получается, сначала копируются символы из буфера data в буфер временной строки, возможно с аллокацией памяти, и лишь
-потом временная строка сравнивается с ”hello”, а потом ещё и деструктор и раскрутка стека на случай исключения.
+Here it turns out that first the characters from the data buffer are copied to the buffer of the temporary string, possibly with memory allocation, and only
+then the temporary string is compared with "hello", and then also the destructor and stack unwinding in case of an exception.
-При использовании же вместо `std::string` `std::string_view` – код на C++ почти не меняется:
+When using `std::string_view` instead of `std::string` – the code in C++ almost does not change:
```cpp
bool is_part_buffer_equal_hello_view(const char* data, int start, int end) {
return std::string_view(data + start, end - start) == "hello";
}
```
-Однако генерируемый машинный код значительно преобразуется, достигая уровня ручного С-кода — там просто сравнивается,
-что end – start == 5 и дальше кусок начального буфера сравнивается через memcmp со строкой ”hello”
-(при -O2 c константами 1819043176 (’hell’) и 111 (’o’)).
-Ни создания временного объекта, ни копирования байтов, ни деструктора, ни раскрутки стека для исключений.
-Убедится можно на https://godbolt.org/z/9fo188e7c
+However, the generated machine code is significantly transformed, reaching the level of manual C-code – there it is simply compared
+that end – start == 5 and then a piece of the initial buffer is compared via memcmp with the string "hello"
+(with -O2 with constants 1819043176 ('hell') and 111 ('o')).
+No creation of a temporary object, no copying of bytes, no destructor, no stack unwinding for exceptions.
+You can verify this at https://godbolt.org/z/9fo188e7c
-Казалось бы, ну вот же в С++17 появился `string_view`, пожалуйста, используй его в параметрах своих функций вместо `const std::string&`,
-и будет счастье. Но тут тоже есть нюанс — всё отлично работает, пока нам не нужно передать строку в стороннее C-API: string_view не даёт
-гарантий нуль-терминированности строки, поэтому его data() нельзя передать в стороннее C-API, и потому всё-равно придётся сначала
-скопировать его в `std::string`. А раз нужен `std::string`, то и параметром функции оптимальнее cделать `const std::string&`
-и далее по цепочке, все параметры вновь станут `const std::string&`.
+It would seem, well, `string_view` appeared in C++17, please, use it in the parameters of your functions instead of `const std::string&`,
+and there will be happiness. But there is also a nuance here – everything works fine, as long as we don’t need to pass the string to a third-party C-API: string_view does not give
+guarantees of null-termination of the string, therefore its data() cannot be passed to a third-party C-API, and therefore you will still have to
+copy it to `std::string` first. And since `std::string` is needed, then it is more optimal to make `const std::string&` the parameter of the function
+and further down the chain, all parameters will again become `const std::string&`.
-### Конкатенация строк
-Далее, после инициализации строки, самая частая мутабельная операция с ними, скорее всего конкатенация строк, либо в виде просто
-сложения строк, либо добавления строки к строке. И именно она легко может вызывать как неоптимальную производительность при неграмотном
-использовании, так и оверхед по памяти, даже при грамотном использовании.
+### String concatenation
+Next, after initializing a string, the most frequent mutable operation with them is most likely string concatenation, either in the form of simply
+adding strings, or adding a string to a string. And it is she who can easily cause both suboptimal performance with illiterate
+use, and memory overhead, even with competent use.
-Рассмотрим простой код ( https://godbolt.org/z/odx7W1Pv7 )
+Consider a simple code ( https://godbolt.org/z/odx7W1Pv7 )
```cpp
#include
void some_outer_function(const std::string&);
@@ -127,9 +128,9 @@
some_outer_function(concat);
}
```
-Как видим, и в clang, и в GCC создается несколько временных объектов, в которые последовательно перекладываются символы строк,
-и как результат — мы получаем несколько лишних аллокаций для промежуточных буферов, символы из строк копируются несколько лишних
-раз из промежуточных буферов. В идеале для лучшей производительности такой код нужно переписать так:
+As we can see, both in clang and in GCC, several temporary objects are created, into which the characters of the strings are sequentially shifted,
+and as a result – we get several extra allocations for intermediate buffers, the characters from the strings are copied several extra
+times from intermediate buffers. Ideally, for better performance, this code needs to be rewritten like this:
```cpp
#include
void some_outer_function(const std::string&);
@@ -145,180 +146,180 @@
}
```
-К сожалению, пока ни один компилятор не оптимизирует первый простой код до уровня второго более оптимального кода, а писать
-такой код каждый раз руками довольно неудобно. То есть опять приходится платить за то, чем не пользуешься.
-Да и в этом случае вполне может возникнуть оверхед по памяти — операции добавления строки обычно во всех реализациях увеличивают
-размер буфера строки не меньше, чем в два раза, считая, что скоро к строке могут снова что-нибудь добавить.
-Поэтому если строку не планируется более модифицировать, но время её жизни ещё не подошло к концу (например, это поле
-какого-либо класса), нужно ещё не забыть сделать на ней `shrink_to_fit`.
+Unfortunately, so far no compiler optimizes the first simple code to the level of the second more optimal code, and writing
+such code every time by hand is quite inconvenient. That is, again you have to pay for what you don’t use.
+And in this case, memory overhead may well occur – string addition operations usually increase
+the size of the string buffer in all implementations by at least two times, assuming that something may soon be added to the string again.
+Therefore, if the string is no longer planned to be modified, but its lifetime has not yet come to an end (for example, this is a field
+of some class), you should not forget to do `shrink_to_fit` on it.
-Между тем, часто основной сценарий использования строк — это как раз некая подготовка строки путём нескольких модификаций и конкатенаций,
-а затем она где-то хранится, более не меняясь. При этом программист обычно знает, примерно какой размер строк ожидается в этом месте,
-и мог бы выделить буфер для этих промежуточных модификаций прямо на стеке, прибегая к динамической аллокации только при превышении
-размера этого буфера. Однако с текущей реализацией строк это сделать довольно проблематично, либо неудобно.
+Meanwhile, often the main scenario for using strings is just some preparation of the string by several modifications and concatenations,
+and then it is stored somewhere, no longer changing. In this case, the programmer usually knows approximately what size of strings is expected in this place,
+and could allocate a buffer for these intermediate modifications directly on the stack, resorting to dynamic allocation only when exceeding
+the size of this buffer. However, with the current implementation of strings, this is quite problematic, or inconvenient.
-Подытожим, что имеем на данный момент:
+Let's summarize what we have at the moment:
-- «Из коробки» в С++ для работы со строками сейчас имеется `std::string`.
-- Строки подразумеваются мутабельными, что приводит к обязательному копированию всех символов строки при инициализации и
- копировании объектов строк.
-- Соответственно, не имеем возможности быстрого копирования строк, даже если не планируем потом менять копию.
-- Конкатенация нескольких строк — задача, могущая выполнятся неоптимально, приводить к оверхеду по памяти, написать оптимальный код сложно.
-- Есть костыль для иммутабельных строк в виде `std::string_view`, однако он не решает вопросы владения строкой, поэтому по сути
- годится только как тип для передачи параметров в функции, не меняющие строки, с оговоркой, что не может использоваться в функциях,
- вызывающих C-API, так как не даёт гарантий нуль-терминированности.
-- Ну и к `std::string` есть вопросы, что несмотря на то, что это класс для строк, собственно для работы со строками в нём крайне куцый
- функционал по сравнению с тем, к чему привыкли в других языках — к примеру нет замены подстрок по шаблону (в других языках это обычно
- replace, но в С++ эта функция делает совершенно другое), trim, split, join, upper, lower и т. п.
- Эти функции приходится каждый раз писать самому, и не факт, что у всех это получится оптимально.
+- "Out of the box" in C++ for working with strings there is now `std::string`.
+- Strings are assumed to be mutable, which leads to mandatory copying of all characters of the string during initialization and
+ copying of string objects.
+- Accordingly, we do not have the ability to quickly copy strings, even if we do not plan to change the copy later.
+- Concatenating several strings is a task that can be performed suboptimally, lead to memory overhead, and it is difficult to write optimal code.
+- There is a crutch for immutable strings in the form of `std::string_view`, but it does not solve the issues of string ownership, so in fact
+ it is only suitable as a type for passing parameters to functions that do not change strings, with the caveat that it cannot be used in functions
+ calling C-API, since it does not guarantee null-termination.
+- Well, and there are questions to `std::string` that despite the fact that this is a class for strings, in fact, for working with strings it has an extremely meager
+ functionality compared to what they are used to in other languages – for example, there is no replacement of substrings by a pattern (in other languages this is usually
+ replace, but in C++ this function does something completely different), trim, split, join, upper, lower, etc.
+ These functions have to be written by yourself every time, and it is not a fact that everyone will be able to do this optimally.
-Надеюсь, после этого небольшого вступления вам будет более понятно, какие проблемы я решал своей строковой библиотекой и каким образом.
+I hope that after this small introduction you will better understand what problems I solved with my string library and how.
-## Библиотека simstr
-Собственно, нельзя сказать, что «я свелосипедил свою реализацию класса для строк».
-Как я ранее показал, сложно, а то и даже невозможно написать один единый строковый класс, хорошо подходящий для всех сценариев
-использования. Именно поэтому у меня не строковый класс, а строковая библиотека, которая содержит несколько разных строковых типов,
-от более простых к более сложным, каждый из которых имеет свои сильные и слабые стороны, и пользователю нужно грамотно подходить к
-вопросу, какой из этих классов в каком случае стоит использовать.
+## Simstr library
+Actually, you can't say that "I reinvented my implementation of the class for strings."
+As I showed earlier, it is difficult, or even impossible, to write one single string class that is well suited for all scenarios
+of use. That is why I don’t have a string class, but a string library, which contains several different string types,
+from simpler to more complex, each of which has its own strengths and weaknesses, and the user needs to competently approach the
+question of which of these classes should be used in which case.
-Саму библиотеку я начал потихоньку разрабатывать ещё в 2011-2012 годах, когда у нас уже появилась семантика перемещения, но ещё
-не было std::string_view. Однако сейчас минимальная версия стандарта для работы библиотеки: **C++20** – используются концепты и \.
+I started developing the library itself little by little back in 2011-2012, when we already had move semantics, but not yet
+there was std::string_view. However, now the minimum standard version for the library to work is: **C++20** – concepts and \ are used.
-Сначала я расскажу о классах библиотеки для самих строк, а потом о том, как в ней оптимально решается задача конкатенации строк.
+First, I will talk about the library classes for the strings themselves, and then about how the string concatenation problem is optimally solved in it.
-Несколько общих моментов:
-- Все классы для работы со строками шаблонизированы типом символов, но подразумевается, что символы могут быть char, char16_t,
+Several general points:
+- All classes for working with strings are templated by the type of characters, but it is assumed that the characters can be char, char16_t,
char32_t, wchar_t.
-- Все строки имеют явную длину.
-- Классы владельцы строк хранят их с завершающим нулем в конце, который не входит в длину строки.
-- В самой строке могут содержаться нулевые символы, все алгоритмы работают только через длину строки, не обращая на них внимания.
-- Классы владельцы строк могут инициализироваться строками другого типа символов, выполняя конвертацию между UTF-8, UTF-16, UTF-32.
-- Для смены регистра символов и сравнения строк без учёта регистра используются встроенные таблицы для первой плоскости юникода
- (до 0xFFFF). Строки считаются представленными в кодировке UTF-8, UTF-16, UTF-32 соответственно.
- Однако не делается нормализация строк и не обрабатываются ситуации, когда смена регистра символа приводит к изменению их количества.
- То есть преобразование регистра символов соответствует `std::towupper`, `std::towlower` для unicode локали,
- только быстрее и может работать с любым видом символов.
- Если вам нужна строгая работа с юникодом, используйте другие средства, например ICU.
+- All strings have an explicit length.
+- The string owner classes store them with a trailing zero at the end, which is not included in the length of the string.
+- The string itself can contain zero characters, all algorithms work only through the length of the string, without paying attention to them.
+- The string owner classes can be initialized with strings of another character type, performing conversion between UTF-8, UTF-16, UTF-32.
+- Built-in tables for the first plane of Unicode are used to change the case of characters and compare strings case-insensitively
+ (up to 0xFFFF). Strings are considered to be represented in UTF-8, UTF-16, UTF-32 encoding, respectively.
+ However, string normalization is not done and situations where changing the case of a character leads to a change in their number are not handled.
+ That is, the case conversion of characters corresponds to `std::towupper`, `std::towlower` for the unicode locale,
+ only faster and can work with any type of characters.
+ If you need strict work with unicode, use other tools, such as ICU.
-### Классы строк.
+### String classes.
-#### Первый самый простой класс строки называется, естественно, `simple_str` :)
+#### The first simplest string class is called, of course, `simple_str` :)
(simstr::simple_str)
-Класс просто представляет собой указатель на начало константной строки и её длину, по сути то же самое, что `std::string_view`.
-Предназначен для работы с иммутабельными строками, не владеющий ими, то есть вы должны сами озаботиться тем, что реальная строка,
-представленная через `simple_str` – жива во время его использования.
+The class simply represents a pointer to the beginning of a constant string and its length, in fact the same as `std::string_view`.
+It is intended for working with immutable strings, not owning them, that is, you must take care that the real string,
+represented through `simple_str` – is alive during its use.
-Реализует все строковые методы, не модифицирующие строку.
+Implements all string methods that do not modify the string.
-Алиасы:
-- `ssa` для simple_str\
-- `ssu` для simple_str\
-- `ssw` для simple_str\
-- `ssuu` для simple_str\
+Aliases:
+- `ssa` for simple_str\
+- `ssu` for simple_str\
+- `ssw` for simple_str\
+- `ssuu` for simple_str\
-Применяется в основном для передачи строк как параметр функций, не модифицирующих переданную строку, вместо `const std::string&`,
-а также для локальных переменных при работе с частями строк.
+It is used mainly for passing strings as a parameter to functions that do not modify the passed string, instead of `const std::string&`,
+as well as for local variables when working with parts of strings.
-#### Второй класс — `simple_str_nt`
+#### The second class is `simple_str_nt`
(simstr::simple_str_nt)
-По устройству и назначению совпадает с `simple_str`, но дает гарантии нуль-терминированности строки.
-То есть если функции надо переданный параметр без изменений передать дальше как C-строку в какое то API, она должна использовать для
-параметра тип `simple_str_nt`.
-Все классы владеющих строк (simstr::sstring, simstr::lstring) могут быть преобразованы в `simple_str_nt`, так как хранят строки с завершающим нулём.
-Это позволяет писать функции с единым типом параметра, принимающим на вход любой тип владеющих строковых объектов.
+In terms of structure and purpose, it coincides with `simple_str`, but guarantees null-termination of the string.
+That is, if the function needs to pass the passed parameter further as a C-string to some API without changes, it should use the
+`simple_str_nt` type for the parameter.
+All classes of owning strings (simstr::sstring, simstr::lstring) can be converted to `simple_str_nt`, since they store strings with a trailing zero.
+This allows you to write functions with a single parameter type that accepts any type of owning string objects as input.
-Алиасы:
-- `stra` для simple_str_nt\
-- `stru` для simple_str_nt\
-- `strw` для simple_str_nt\
-- `struu` для simple_str_nt\
+Aliases:
+- `stra` for simple_str_nt\
+- `stru` for simple_str_nt\
+- `strw` for simple_str_nt\
+- `struu` for simple_str_nt\
-Может инициализироваться строковыми литералами:
+Can be initialized with string literals:
```cpp
stra text = "Text";
```
-Длина в этом случае вычисляется сразу при компиляции. Аналогично `simple_str_nt` создается с помощью `operator""_ss`:
+In this case, the length is calculated immediately at compile time. Similarly, `simple_str_nt` is created using `operator""_ss`:
```cpp
stringa result = "Count: "_ss + count;
```
-#### Класс sstring (shared string).
+#### Sstring class (shared string).
(simstr::sstring)
-Класс, умеющий хранить иммутабельную строку.
-То есть ему можно присвоить некую строку только целиком, модифицировать символы строки нельзя.
+A class that can store an immutable string.
+That is, you can only assign a string to it entirely, you cannot modify the characters of the string.
-Владеет строкой, управляет памятью для символов строки.
-Хранит со строками завершающий нуль, и может быть источником для `simple_str_nt`, для передачи в C-API.
-Так же, как и `simple_str`, реализует все методы, не модифицирующие строку.
+Owns the string, manages the memory for the characters of the string.
+Stores a trailing zero with the strings, and can be a source for `simple_str_nt`, for passing to C-API.
+Like `simple_str`, it implements all methods that do not modify the string.
-Алиасы:
-- `stringa` для sstring\
-- `stringu` для sstring\
-- `stringw` для sstring\
-- `stringuu` для sstring\
+Aliases:
+- `stringa` for sstring\
+- `stringu` for sstring\
+- `stringw` for sstring\
+- `stringuu` for sstring\
-То, что хранимая строка иммутабельна, позволяет применить ряд оптимизаций:
-- Для строк, не подходящих для SSO, использует общий разделяемый буфер с атомарным счётчиком ссылок.
- Позволяет быстро копировать строку без необходимости блокировок доступа к содержимому буфера.
-- Нет необходимости хранить размер буфера (capacity) — всё равно мы ничего не дописываем в буфер.
-- Позволяет просто ссылаться на литералы программы, не копируя их символы в какой-либо буфер:
+The fact that the stored string is immutable allows you to apply a number of optimizations:
+- For strings that are not suitable for SSO, it uses a common shared buffer with an atomic reference counter.
+ Allows you to quickly copy a string without the need to block access to the contents of the buffer.
+- There is no need to store the buffer size (capacity) – we are not adding anything to the buffer anyway.
+- Allows you to simply refer to program literals without copying their characters to any buffer:
```cpp
- stringa str = "Hello!"; // Ничего не стоит, не копирует байты строки
- stringa ltr = stra{"Hello!"}; // А вот тут копирует байты строки в ltr
+ stringa str = "Hello!"; // Costs nothing, does not copy the bytes of the string
+ stringa ltr = stra{"Hello!"}; // But here it copies the bytes of the string to ltr
```
-Также в классе применяется **SSO** – Small String Optimization.
-Короткие строки помещаются внутри самого объекта во внутренний буфер.
+The class also uses **SSO** – Small String Optimization.
+Short strings are placed inside the object itself in an internal buffer.
-Размеры:
+Sizes:
-Для 64 бит:
-- `stringa` – класс 24 байта, SSO до 23 символов.
-- `stringu` – класс 32 байта, SSO до 15 символов.
-- `stringuu` – класс 32 байта, SSO до 7 символов.
+For 64 bits:
+- `stringa` – class 24 bytes, SSO up to 23 characters.
+- `stringu` – class 32 bytes, SSO up to 15 characters.
+- `stringuu` – class 32 bytes, SSO up to 7 characters.
-Для 32 бит:
-- `stringa` – класс 16 байт, SSO до 15 символов.
-- `stringu` – класс 24 байта, SSO до 11 символов.
-- `stringuu` – класс 24 байта, SSO до 5 символов.
+For 32 bits:
+- `stringa` – class 16 bytes, SSO up to 15 characters.
+- `stringu` – class 24 bytes, SSO up to 11 characters.
+- `stringuu` – class 24 bytes, SSO up to 5 characters.
-#### Класс lstring (local string)
+#### Class lstring (local string)
(simstr::lstring)
-Класс, хранящий строку и позволяющий её модифицировать.
-Владеет строкой, управляет памятью для символов строки.
-Хранит со строками завершающий нуль, и может быть источником для `simple_str_nt`, для передачи в C-API.
-Как и все остальные классы, реализует все методы, не модифицирующие строку.
+A class that stores a string and allows it to be modified.
+Owns the string, manages the memory for the characters of the string.
+Stores a trailing zero with the strings, and can be a source for `simple_str_nt`, for passing to C-API.
+Like all other classes, it implements all methods that do not modify the string.
-В качестве `N` в параметре шаблона задаётся размер внутреннего буфера для хранения символов.
-Строки длиной до N символов хранятся внутри объекта, а при превышении этого количества — аллоцируется динамический буфер,
-в который сохраняются символы. При копировании объекта все символы также всегда копируются.
+The size of the internal buffer for storing characters is specified as `N` in the template parameter.
+Strings up to N characters long are stored inside the object, and when this number is exceeded, a dynamic buffer is allocated,
+in which the characters are saved. When copying an object, all characters are also always copied.
-Если `forShare` == true и символы не помещаются в локальный буфер, то динамический буфер создается с дополнительным местом,
-так чтобы совпадать по структуре с буфером `sstring`. Тогда при перемещении `lstring` в `sstring` – переместится только указатель
-на буфер, без излишнего копирования символов.
+If `forShare` == true and the characters do not fit into the local buffer, then a dynamic buffer is created with additional space,
+so that it matches the structure of the `sstring` buffer. Then, when moving `lstring` to `sstring` – only the pointer will move
+to the buffer, without unnecessary copying of characters.
-Этот класс удобен для работы со строками как локальная переменная на стеке.
-Обычно мы предполагаем примерный размер строк, с котороми будем работать, и можем создать локальную строку с буфером на стеке,
-и работать с ней. При этом не опасаясь переполнения буфера, так как в этом случае строка переключится на динамический буфер.
+This class is convenient for working with strings as a local variable on the stack.
+Usually we assume the approximate size of the strings we will be working with, and we can create a local string with a buffer on the stack,
+and work with it. At the same time, without fear of buffer overflow, since in this case the string will switch to a dynamic buffer.
-Алиасы:
-- `lstringa` для lsrting\
-- `lstringu` для lsrting\
-- `lstringw` для lsrting\
-- `lstringuu` для lsrting\
-- `lstringsa` для lsrting\
-- `lstringsu` для lsrting\
-- `lstringsw` для lsrting\
-- `lstringsuu` для lsrting\
+Aliases:
+- `lstringa` for lsrting\
+- `lstringu` for lsrting\
+- `lstringw` for lsrting\
+- `lstringuu` for lsrting\
+- `lstringsa` for lsrting\
+- `lstringsu` for lsrting\
+- `lstringsw` for lsrting\
+- `lstringsuu` for lsrting\
-Небольшой пример использования с пояснениями:
+A small example of use with explanations:
```cpp
#ifdef _WIN32
const char path_separator = '\\';
@@ -329,13 +330,13 @@
auto get_current_dir() {
#ifdef _WIN32
- /* заполняем буфер wchar_t строки lstringw из GetCurrentDirectoryW с возможным
- увеличением буфера и конвертируем в ut8 char. В конструкторе используется то, что появилось
- только в С++23 как `resize_and_overwrite`, а у нас было изначально :) */
+ /* fills the buffer of the wchar_t string lstringw from GetCurrentDirectoryW with possible
+ increasing the buffer and converting to ut8 char. The constructor uses what appeared
+ only in C++23 as `resize_and_overwrite`, and we had it originally :) */
lstringa path{lstringw{ [](auto p, auto s) { return GetCurrentDirectoryW(DWORD(s + 1), p); }}};
- /* Эта одна строчка делает примерно то же самое, что и вот такой код.
+ /* This one line does approximately the same thing as this code.
typedef struct lstringa_MAX_PATH_t {
char* data;
size_t length;
@@ -347,11 +348,11 @@
wchar_t buffer[MAX_PATH + 1], *buf = buffer;
DWORD size = sizeof(buffer) / sizeof(wchar_t), lengthOfpath;
for (;;) {
- // Возвращает либо количество скопированных символов без учёта завершающего нуля,
- // либо если буфер мал, то нужный размер буфера вместе с завершающим нулём
+ // Returns either the number of copied characters without taking into account the trailing zero,
+ // or if the buffer is small, then the required buffer size along with the trailing zero
DWORD ret = GetCurrentDirectoryW(size, buf);
if (ret < size) {
- // Влезло в буфер, хотя в Windows пути могут быть и длиннее, чем MAX_PATH, если начинаются с \\?\
+ // Fits into the buffer, although in Windows paths can be longer than MAX_PATH if they start with \\?\
// https://learn.microsoft.com/ru-ru/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
lenOfpath = ret;
break;
@@ -371,14 +372,14 @@
lstringa path{ [](char* p, size_t s) {
const char* res = getcwd(p, s + 1);
if (res) {
- return stra{res}.length(); // Возвращаем длину строки
+ return stra{res}.length(); // Returns the length of the string
}
- if (errno == ERANGE) // Не влезло в буфер, попробуем в два раза больше
+ if (errno == ERANGE) // Did not fit into the buffer, let's try twice as much
return s * 2;
return 0ul;
}};
#endif
- // Удостоверимся, что строка будет заканчиваться разделителем директорий
+ // Let's make sure that the string will end with a directory separator
if (!path.length() || path.at(-1) != path_separator) {
path += e_c(1, path_separator);
}
@@ -388,37 +389,37 @@
stringa build_full_path(ssa fileName) {
return get_current_dir() + fileName + ".txt";
/*
- Здесь сначала на стеке создастся временный объект lstringa для вызова get_current_dir.
- Функция get_current_dir заполнит его названием текущего каталога.
- В 99.9% случаев для этого хватит локального буфера на стеке.
- После рассчитывается общая длина для результата: длина current_dir + длина fileName + 4.
- Определяется буфер для строки конечного результата - если длина меньше 24 — строка будет размещена прямо в stringa,
- иначе аллоцируется буфер для результирующей строки сразу нужного размера.
- Затем в буфер результирующей строки последовательно копируются символы из current_dir, file_name, ".txt";
- Ну и благодаря RVO - место для самого результата (stringa) - отводится в вызывающей функции,
- то есть никакого дополнительного копирования при возврате не будет.
+ Here, a temporary lstringa object will first be created on the stack to call get_current_dir.
+ The get_current_dir function will fill it with the name of the current directory.
+ In 99.9% of cases, the local buffer on the stack will be enough for this.
+ After that, the total length for the result is calculated: the length of current_dir + the length of fileName + 4.
+ The buffer for the string of the final result is determined - if the length is less than 24 - the string will be placed directly in stringa,
+ otherwise a buffer for the resulting string is allocated immediately of the required size.
+ Then the characters from current_dir, file_name, ".txt" are sequentially copied to the buffer of the resulting string;
+ Well, thanks to RVO - the place for the result itself (stringa) - is allocated in the calling function,
+ that is, there will be no additional copying upon return.
- Таким образом, будет максимум всего две аллокации памяти (если current_dir не влезет в MAX_PATH),
- или одна, если результирующая строка длиннее 23 символов, при этом эта аллокация будет сразу нужного размера.
+ Thus, there will be a maximum of only two memory allocations (if current_dir does not fit into MAX_PATH),
+ or one, if the resulting string is longer than 23 characters, while this allocation will be immediately of the required size.
*/
}
```
-В этом примере вы наверняка заметили, как конкатенируются строки и задались вопросом — как же при двух сложениях считалась
-длина всего результата, чтобы выделить необходимое место сразу за один раз, без промежуточных буферов?
+In this example, you probably noticed how strings are concatenated and wondered – how was the
+length of the entire result calculated with two additions in order to allocate the necessary space at once, without intermediate buffers?
-Ответ на этот вопрос:
+The answer to this question:
-### Строковые выражения
-Дело в том, что в библиотеке нет сложения строковых объектов как такового. Сложение выполняется для «строковых выражений».
+### String Expressions
+The fact is that there is no addition of string objects as such in the library. Addition is performed for "string expressions".
-*Строковое выражение* — это любой объект произвольного типа, имеющий функции `length` и `place`.
-Функция `length` – возвращает длину строки, функция `place` – помещает символы строки в переданный ей буфер.
+A *string expression* is any object of arbitrary type that has `length` and `place` functions.
+The `length` function returns the length of the string, and the `place` function places the characters of the string into the buffer passed to it.
-Любая владеющая строка (simstr::sstring, simstr::lstring) может инициализироваться строковым выражением — она запрашивает у него длину,
-выделяет место для хранения символов, и передает это место строковому выражению, вызывая его функцию place.
+Any owning string (simstr::sstring, simstr::lstring) can be initialized with a string expression — it requests its length,
+allocates space for storing characters, and passes this space to the string expression, calling its place function.
-Для строковых выражений определена шаблонная функция сложения:
+A template addition function is defined for string expressions:
```cpp
template B>
inline auto operator + (const A& a, const B& b) {
@@ -426,10 +427,10 @@
}
```
-`strexprjoin` – шаблонный тип, который сам является строковым выражением.
-В себе он хранит ссылки на два переданных ему строковых выражения.
-При запросе длины он выдает сумму длин двух строковых выражений, а при размещении символов — сначала размещает
-в переданном буфере первое выражение, затем второе.
+`strexprjoin` is a template type that is itself a string expression.
+It stores references to the two string expressions passed to it.
+When the length is requested, it returns the sum of the lengths of the two string expressions, and when placing characters, it first places
+the first expression in the passed buffer, then the second.
```cpp
template B>
struct strexprjoin {
@@ -441,86 +442,83 @@
constexpr symb_type* place(symb_type* p) const noexcept { return b.place(a.place(p)); }
};
```
-Таким образом, операция сложения строковых выражений создает объект, также являющийся строковым выражением,
-к которому также может быть применена следующая операция сложения, и который рекурсивно хранит ссылки на слагаемые части,
-каждая из которых знает свой размер и умеет размещать себя в буфере результата. И так далее, к каждому получаемому
-строковому выражению можно снова применить `operator +`, формируя цепочку из нескольких строковых выражений,
-и в итоге "материализовать" последний получившийся объект, который сначала посчитает размер всей общей памяти для
-конечного результата, а затем разместит вложенные подвыражения в один буфер.
+Thus, the addition operation of string expressions creates an object that is also a string expression,
+to which the next addition operation can also be applied, and which recursively stores references to the component parts,
+each of which knows its size and knows how to place itself in the result buffer. And so on, to each resulting
+string expression, you can reapply `operator +`, forming a chain of several string expressions,
+and eventually "materialize" the last resulting object, which first calculates the size of the entire total memory for
+the final result, and then places the nested subexpressions into one buffer.
-Все строковые типы библиотеки сами являются строковыми выражениями, то есть могут служить слагаемыми в конкатенациях
-строковых выражений.
+All string types in the library are themselves string expressions, that is, they can serve as terms in concatenations
+of string expressions.
-Также `operator+` определён для строковых выражений и строковых литералов, строковых выражений и чисел (числа конвертируются
-в десятичное представление), а также вы можете сами добавить желаемые типы.
+Also, `operator+` is defined for string expressions and string literals, string expressions and numbers (numbers are converted
+to decimal representation), and you can add the desired types yourself.
-Пример:
+Example:
```cpp
stringa text = header + " count=" + count + ", done";
```
-Существует несколько типов строковых выражений "из коробки", для выполнения различных операций со строками:
+There are several types of string expressions "out of the box" for performing various operations on strings:
-#### expr_spaces<ТипСимвола, КоличествоСимволов, Символ = ' '>{}
-Выдает строку длиной КоличествоСимволов, заполненную заданным символом. Количество символов и символ - константы времени
-компиляции. Для некоторых случаев есть сокращенная запись:
-
- e_spca(КоличествоСимволов) - строка char пробелов
- e_spcw(КоличествоСимволов) - строка w_char пробелов
+#### expr_spaces{}
+Returns a string of length NumberOfCharacters, filled with the specified character. The number of characters and the symbol are compile-time constants. For some cases, there is a shorthand notation:
-#### expr_pad<ТипСимвола>{КоличествоСимволов, Символ = ' '}
-Выдает строку длиной КоличествоСимволов, заполненную заданным символом.
-Количество символов и символ могут задаваться в рантайме. Сокращенная запись:
+ e_spca(NumberOfCharacters) - string of char spaces
+ e_spcw(NumberOfCharacters) - string of w_char spaces
- e_c(КоличествоСимволов, Символ)
+#### expr_pad{NumberOfCharacters, Symbol = ' '}
+Returns a string of length NumberOfCharacters, filled with the specified character.
+The number of characters and the symbol can be specified at runtime. Shorthand notation:
+
+ e_c(NumberOfCharacters, Symbol)
#### e_choice(bool Condition, StrExpr1, StrExpr2)
-Если Condition == true, результат будет равен StrExpr1, иначе StrExpr2.
+If Condition == true, the result will be StrExpr1, otherwise StrExpr2.
#### e_if(bool Condition, StrExpr1)
-Если Condition == true, результат будет равен StrExpr1, иначе пустая строка.
+If Condition == true, the result will be StrExpr1, otherwise an empty string.
-#### expr_num<ТипСимвола>(ЦелоеЧисло)
-Конвертирует число в десятичное представление. Редко используется, так как для строковых выражений и чисел
-переопределен оператор "+", и число можно просто написать как `text + number`;
+#### expr_num(Integer)
+Converts a number to decimal representation. Rarely used, since the "+" operator is overloaded for string expressions and numbers, and the number can simply be written as `text + number`;
-#### expr_real<ТипСимвола>(ВещественноеЧисло)
-конвертирует число в десятичное представление. Редко используется, так как для строковых выражений и чисел
-переопределен оператор "+", и число можно просто написать как `text + number`;
+#### expr_real(RealNumber)
+converts a number to decimal representation. Rarely used, since the "+" operator is overloaded for string expressions and numbers, and the number can simply be written as `text + number`;
-#### e_join(контейнер, "Разделитель")
-Конкатенирует все строки в контейнере, используя разделитель. Если ПослеПоследнего == true,
-то разделитель добавляется и после последнего элемента контейнера, иначе только между элементами.
-Если ТолькоНеПустые == true, то пустые строки пропускаются без добавления разделителя.
+#### e_join(container, "Separator")
+Concatenates all strings in the container, using a separator. If AfterLast == true,
+then the separator is added after the last element of the container as well, otherwise only between elements.
+If OnlyNotEmpty == true, then empty strings are skipped without adding a separator.
-#### e_repl(ИсходнаяСтрока, "Искать", "Заменять")
-Заменяет в исходной строке вхождения "Искать" на "Заменять".
-Шаблоны поиска и замены - строковые литералы времени компиляции.
+#### e_repl(OriginalString, "Search", "Replace")
+Replaces occurrences of "Search" in the original string with "Replace".
+Search and replace patterns are compile-time string literals.
-#### expr_replaced<ТипСимвола>{ИсходнаяСтрока, Искать, Заменять}
-Заменяет в исходной строке вхождения Искать на Заменять.
-Шаблоны поиска и замены - могут быть любыми строковыми объектами в рантайме.
+#### expr_replaced{OriginalString, Search, Replace}
+Replaces occurrences of Search in the original string with Replace.
+Search and replace patterns can be any string objects at runtime.
-#### empty_expr<ТипСимвола>
-Выдает пустую строку. Сокращённая запись — eea, eeu, eew, eeuu. Применяется если формирование строки начинается с числа и строкового литерала:
+#### empty_expr
+Returns an empty string. Abbreviated notation — eea, eeu, eew, eeuu. Used if the string formation starts with a number and a string literal:
```cpp
str = eea + count + " times.";
```
-так как оператор сложения определён только для сложения строкового выражения и числа.
-Также замечу, что существует `operator""_ss`, который превращает строковый литерал в объект `simple_str_nt`, который уже является строковым выражением:
+since the addition operator is only defined for adding a string expression and a number.
+I also note that there is `operator""_ss`, which turns a string literal into a `simple_str_nt` object, which is already a string expression:
```cpp
str = "Count = "_ss + count;
...
str = count + " times."_ss;
```
-#### Свои строковые выражения
-Вы можете сами создавать свои типы строковых выражений для оптимального формирования строк в нужных вам целях и алгоритмах.
-Для этого просто создайте тип с методами `length`, `place` и `typename symb_type`.
-Примеры создания и использования из реальных проектов:
+#### Your own string expressions
+You can create your own string expression types to optimally form strings for your specific purposes and algorithms.
+To do this, simply create a type with `length`, `place` and `typename symb_type` methods.
+Examples of creation and use from real projects:
```cpp
-/* Сформировать строку в JSON формате, в 16 битных символах */
+/* Form a string in JSON format, in 16-bit characters */
struct expr_json_str {
using symb_type = u16s;
ssu text;
@@ -607,7 +605,7 @@ inline u16s* expr_json_str::place(u16s* ptr) const noexcept {
}
```
-Использование:
+Usage:
```cpp
........
@@ -617,9 +615,9 @@ vtText << uR"({"#type":"jxs:string","#value":")" + expr_json_str(name) + u"\"}";
.......
```
-Ещё пример
+Another example
```cpp
-/* Нужно сформировать бинарные данные в BASE64 формате, в 16 битных символах */
+/* Need to form binary data in BASE64 format, in 16-bit characters */
struct expr_str_base64 {
using symb_type = u16s;
ssa text;
@@ -660,7 +658,7 @@ inline u16s* expr_str_base64::place(u16s* ptr) const noexcept {
}
```
-Использование:
+Usage:
```cpp
......
chunked_string_builder vtText;
@@ -669,10 +667,10 @@ vtText << u"{\"#\",87126200-3e98-44e0-b931-ccb1d7edc497,{1,{#base64:" + expr_str
......
```
-И ещё
+And more
```cpp
-/* Нужно преобразовать tm в строку даты/времени в 16-битных символах */
+/* Need to convert tm to a date/time string in 16-bit characters */
struct expr_str_tm {
using symb_type = u16s;
const tm& t;
@@ -685,11 +683,11 @@ struct expr_str_tm {
inline u16s* expr_str_tm::place(u16s* ptr) const noexcept {
if constexpr (sizeof(wchar_t) == 2) {
- // Под Windows можно сразу форматнуть строку в нужный буфер
+ // Under Windows, you can immediately format the string into the desired buffer
std::swprintf((wchar_t*)ptr, 20, L"%04i-%02i-%02i %02i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
} else {
- // Сначала форматнём в промежуточный буфер, потом скопируем в результат
+ // First, format into an intermediate buffer, then copy to the result
char buf[20];
std::snprintf(buf, 20, "%04i-%02i-%02i %02i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
t.tm_min, t.tm_sec);
@@ -701,7 +699,7 @@ inline u16s* expr_str_tm::place(u16s* ptr) const noexcept {
}
```
-Использование
+Usage
```cpp
......
@@ -717,31 +715,31 @@ bool makeBind(SqliteQuery& query, tVariant& param, unsigned paramNum) {
......
```
-ВНИМАНИЕ: обычно поля в объектах строковых выражений являются ссылками на исходные данные.
-И ссылки эти почти всегда ведут на локальные или временные объекты. Поэтому крайне рискованно возвращать строковые выражения
-из функций — надо сто раз проверить, что в них не попали ссылки на локальные или временные переменные.
-Возьмите за правило — можно легко передавать строковые выражения в функции, и опасно возвращать их из функций.
-Лучше при возврате материализовать строковое выражение в строковый объект, содержащий итоговую строку.
-При желании тип возвращаемой строки можно задать шаблонным параметром.
-
+ATTENTION: usually the fields in string expression objects are references to the source data.
+And these references almost always lead to local or temporary objects. Therefore, it is extremely risky to return string expressions
+from functions — you need to check a hundred times that they do not contain references to local or temporary variables.
+Make it a rule — you can easily pass string expressions to functions, and it is dangerous to return them from functions.
+It is better to materialize a string expression into a string object containing the final string when returning.
+If desired, the type of the returned string can be specified by a template parameter.
-### Класс chunked_string_builder
-Предназначен для конкатенации множества строк.
-Когда вам нужно последовательно формировать длинный текст из множества небольших кусочков (например, формируете html ответ
-и т. п.) - последовательно складывать всё в один строковый объект крайне неоптимально — будет много переаллокаций и
-перекопирования уже накопленных символов. В этом случае удобно использовать chunked_string_builder — всё, что он умеет,
-это прибавлять строку к накопленным символам. Однако делает он это не в единый последовательный буфер памяти, а в отдельные
-буфера, не меньше чем заданное выравнивание. При заполнении очередного буфера он просто создает ещё один буфер и продолжает
-складывать данные в него.
+### Class chunked_string_builder
-То есть допустим вы задали выравнивание 1024.
-Добавили несколько строк, заполнили буфер на 100 символов. И добавляете строку длинной 3000 символов.
-При этом 924 символа скопируются в первый буфер, заполнив его до конца.
-Для оставшихся 2076 создастся буфер размером 3072 символа, и они скопируются в него, в нём останется место для 996 символов.
-Так последовательно каждый буфер заполняется до конца, и имеет размер кратный заданному выравниванию.
-Таким образом избегаются переаллокации и перекопирование обработанных символов.
+Designed for concatenating multiple strings.
+When you need to sequentially form a long text from many small pieces (for example, you are forming an html response
+etc.) - sequentially adding everything to one string object is extremely suboptimal - there will be many reallocations and
+copying of already accumulated characters. In this case, it is convenient to use chunked_string_builder - all it can do is
+add a string to the accumulated characters. However, it does this not in a single sequential memory buffer, but in separate
+buffers, no less than the specified alignment. When filling the next buffer, it simply creates another buffer and continues
+to add data to it.
-После окончательного заполнения вы можете работать с накопленными данными — либо слить все буфера в одну последовательную
-строку (размер для буфера которой вы теперь уже знаете), либо перебирать их по отдельности, например, посылая эти буфера
-в сеть. Либо последовательно копируя данные в буфер заданного размера.
+That is, suppose you set the alignment to 1024.
+Added several strings, filled the buffer with 100 characters. And you add a string of 3000 characters long.
+In this case, 924 characters will be copied to the first buffer, filling it to the end.
+For the remaining 2076, a buffer of 3072 characters will be created, and they will be copied into it, leaving space for 996 characters in it.
+Thus, each buffer is sequentially filled to the end and has a size that is a multiple of the specified alignment.
+This avoids reallocations and copying of processed characters.
+
+After the final filling, you can work with the accumulated data - either merge all the buffers into one sequential
+string (the size for the buffer of which you now already know), or iterate over them separately, for example, sending these buffers
+to the network. Or sequentially copying data into a buffer of a given size.
diff --git a/docs/overview_ru.md b/docs/overview_ru.md
new file mode 100644
index 0000000..d804244
--- /dev/null
+++ b/docs/overview_ru.md
@@ -0,0 +1,749 @@
+# Строки в С++
+(, что с вами не так?)
+
+[On English|По-английски](overview.md)
+
+
+В ретроспективе 1991 года по истории C++ его создатель Бьярне Страуструп назвал отсутствие стандартного строкового типа
+(и некоторых других стандартных типов) в C++ 1.0 худшей ошибкой, которую он допустил при его разработке:
+"the absence of those led to everybody re-inventing the wheel and to an unnecessary diversity in the most fundamental classes"
+(«Их отсутствие привело к тому, что все заново изобретали велосипед, и к ненужному разнообразию в самых фундаментальных классах»).
+
+
+## Что было и есть
+Во вступительной части я хочу немного описать, каково ныне состояние со строками в С++, как мы к нему докатились и почему оно таково.
+А также описать недостатки текущих реализаций, чтобы были понятны решения, которые я использую в своей библиотеке строк.
+
+Собственно, изначально как такового стандартного типа для строк в С++ не было.
+Для работы со строками использовался подход из C – строка есть указатель на массив байтов, оканчивающихся нулём.
+Недостатки таких строк — невозможно в строке использовать байт `0`, т. е. не подходит для бинарных данных,
+непонятна стратегия управления/владения ресурсами, ну и основной недостаток — длину строки приходится вычислять каждый раз,
+перебирая все её символы.
+
+Откуда ноги растут у такого решения вполне понятно — со времён динозавров: как динозавры были большие, с маленьким мозгом и
+короткими ручками, так и компьютеры были большие, память у них была маленькая, а строки короткими. Сэкономить память на хранении длины строки было важнее, чем потерять время на повторный подсчет длины.
+
+Первые попытки стандартизировать строки как класс начались только в С++98 - std::string появился, как часть STL, и как
+многое из STL, крайне неоднозначно воспринимался программистами.
+
+И первое, что приходит в голову при улучшении C-строк — надо хранить длину строки:
+```cpp
+ struct simple_string {
+ const char* data;
+ size_t length;
+ };
+```
+
+При наличии такой строки, уже множество алгоритмов значительно оптимизируются.
+Например, при сравнении двух строк на равенство мы можем даже не начинать сравнивать их символы, если длины строк не равны.
+Более того, этих данных абсолютно достаточно для всех методов, которые не модифицируют строку.
+Также заметим, что такой объект на современных 64-битных архитектурах прекрасно передается в функции по значению —
+оба его поля укладываются в регистры (ну, кроме windows), что облегчает работу оптимизатору компилятора.
+
+Между тем, такое решение попало в стандарт только аж в С++17, в виде `std::string_view`.
+Видимо, только тогда до комитета смогли донести мысль, что строки строкам рознь, и использовать только один универсальный объект
+для строк — по меньшей мере может приводить к уменьшению производительности, а также нарушает принцип «не плати за то,
+чем не пользуешься». Почему же «строки строкам рознь» и почему нам мало одного типа для строки, рассмотрим как раз далее.
+
+### Ресурсы
+И следующий вопрос, возникающий со строками — это владение ресурсами.
+Практически каждый крупный фреймворк решал эту задачу самостоятельно, изобретая свои велосипеды.
+У нас есть `std::string`, в QT у нас `QString`, в MFC - `CString`, в ATL - `CAtlString`, свои строки есть в Folly,
+в общем, “тысячи их”, любой игровой движок начинают с того, чтобы написать свои строки.
+
+Многие из этих реализаций в аспекте управления ресурсами для улучшения производительности использовали подход
+**COW** – “Copy On Write”. При этом объект строки ссылался на некий разделяемый между несколькими объектами буфер с символами
+строки и счётчиком ссылок на этот буфер, что позволяло быстро создавать копию строки, а реально копировать символы только
+при её модификации.
+
+Но все они совпадали в одном — строка всегда предполагалась мутабельной, то есть что мы можем модифицировать символы в буфере строки.
+
+### Мутабельность / иммутабельность
+Из-за этого подход **COW** умер к С++11: при каждой операции, могущей модифицировать символы строки приходилось проверять,
+не ссылаемся ли мы на разделяемый буфер и если да, то копировать символы в другой буфер.
+В многопоточной же среде потом ещё и проверять, не надо ли теперь освобождать старый буфер, и естественно всё это обмазавшись
+локами или атомиками, что тоже не бесплатно.
+Поэтому, начиная с С++11 `std::string` не использует **COW**, и каждое копирование объекта строки приводит и к копированию
+всех символов строки в другой буфер.
+
+Естественно, что каждый новый буфер требует аллокации памяти, что пытаются немного оптимизировать за счёт **SSO** –
+“Small String Optimization”, когда объект строки содержит внутри себя небольшой буфер и символы коротких строк
+располагаются прямо в нём.
+Но это уже зависит от реализации: в одних библиотеках помещают в объект строки до 15 байт, в некоторых до 23.
+Однако эта оптимизация тоже палка о двух концах, и может в различных реализациях усложнить перемещение строки - если она хранит
+указатель на свой внутренний буфер, его придётся корректировать.
+
+А без COW мутабельность строк приводит к тому, что любая инициализация объекта строки приводит к копированию байтов.
+Посмотрим такой код:
+```cpp
+ const char* text1 = "Hello, World"; // ничего не стоит
+ std::string_view text2 = "Hello, World"; // Ничего не стоит, вычисляет длину строки при компиляции
+ std::string text3 = "Hello, World"; // В рантайме каждый раз копирует символы строки
+```
+
+(Удостоверится в правдивости комментариев можно на https://godbolt.org/z/51oKGWT5T )
+
+Но если нам дальше по коду не нужно никак модифицировать строку, мы зря платим за аллокацию, копирование символов,
+а также за деструктор строки. То есть хотелось бы иметь как минимум два варианта строк — мутабельные и иммутабельные,
+чтобы явно дать понять компилятору, что мы не собираемся модифицировать строку.
+Или банальный пример — мы парсим какой-то входящий буфер данных, нам нужно проверить, равен ли некий кусок буфера строке
+”hello” на «чистом С++», т. е. без всяких memcmp и strcmp. До появления string_view приходилось делать примерно так:
+```cpp
+ bool is_part_buffer_equal_hello(const char* data, int start, int end) {
+ return std::string(data + start, end - start) == "hello";
+ }
+```
+Тут получается, сначала копируются символы из буфера data в буфер временной строки, возможно с аллокацией памяти, и лишь
+потом временная строка сравнивается с ”hello”, а потом ещё и деструктор и раскрутка стека на случай исключения.
+
+При использовании же вместо `std::string` `std::string_view` – код на C++ почти не меняется:
+```cpp
+ bool is_part_buffer_equal_hello_view(const char* data, int start, int end) {
+ return std::string_view(data + start, end - start) == "hello";
+ }
+```
+Однако генерируемый машинный код значительно преобразуется, достигая уровня ручного С-кода — там просто сравнивается,
+что end – start == 5 и дальше кусок начального буфера сравнивается через memcmp со строкой ”hello”
+(при -O2 c константами 1819043176 (’hell’) и 111 (’o’)).
+Ни создания временного объекта, ни копирования байтов, ни деструктора, ни раскрутки стека для исключений.
+Убедится можно на https://godbolt.org/z/9fo188e7c
+
+Казалось бы, ну вот же в С++17 появился `string_view`, пожалуйста, используй его в параметрах своих функций вместо `const std::string&`,
+и будет счастье. Но тут тоже есть нюанс — всё отлично работает, пока нам не нужно передать строку в стороннее C-API: string_view не даёт
+гарантий нуль-терминированности строки, поэтому его data() нельзя передать в стороннее C-API, и потому всё-равно придётся сначала
+скопировать его в `std::string`. А раз нужен `std::string`, то и параметром функции оптимальнее cделать `const std::string&`
+и далее по цепочке, все параметры вновь станут `const std::string&`.
+
+### Конкатенация строк
+Далее, после инициализации строки, самая частая мутабельная операция с ними, скорее всего конкатенация строк, либо в виде просто
+сложения строк, либо добавления строки к строке. И именно она легко может вызывать как неоптимальную производительность при неграмотном
+использовании, так и оверхед по памяти, даже при грамотном использовании.
+
+Рассмотрим простой код ( https://godbolt.org/z/odx7W1Pv7 )
+```cpp
+ #include
+ void some_outer_function(const std::string&);
+
+ void func(const std::string& s1, const std::string& s2) {
+ std::string concat = s1 + s2 + "hello";
+ some_outer_function(concat);
+ }
+```
+Как видим, и в clang, и в GCC создается несколько временных объектов, в которые последовательно перекладываются символы строк,
+и как результат — мы получаем несколько лишних аллокаций для промежуточных буферов, символы из строк копируются несколько лишних
+раз из промежуточных буферов. В идеале для лучшей производительности такой код нужно переписать так:
+```cpp
+ #include
+ void some_outer_function(const std::string&);
+
+ void func(const std::string& s1, const std::string& s2) {
+ static const std::string_view hello = "hello";
+ std::string concat;
+ concat.reserve(s1.size() + s2.size() + hello.size());
+ concat += s1;
+ concat += s2;
+ concat += hello;
+ some_outer_function(concat);
+ }
+```
+
+К сожалению, пока ни один компилятор не оптимизирует первый простой код до уровня второго более оптимального кода, а писать
+такой код каждый раз руками довольно неудобно. То есть опять приходится платить за то, чем не пользуешься.
+Да и в этом случае вполне может возникнуть оверхед по памяти — операции добавления строки обычно во всех реализациях увеличивают
+размер буфера строки не меньше, чем в два раза, считая, что скоро к строке могут снова что-нибудь добавить.
+Поэтому если строку не планируется более модифицировать, но время её жизни ещё не подошло к концу (например, это поле
+какого-либо класса), нужно ещё не забыть сделать на ней `shrink_to_fit`.
+
+Между тем, часто основной сценарий использования строк — это как раз некая подготовка строки путём нескольких модификаций и конкатенаций,
+а затем она где-то хранится, более не меняясь. При этом программист обычно знает, примерно какой размер строк ожидается в этом месте,
+и мог бы выделить буфер для этих промежуточных модификаций прямо на стеке, прибегая к динамической аллокации только при превышении
+размера этого буфера. Однако с текущей реализацией строк это сделать довольно проблематично, либо неудобно.
+
+Подытожим, что имеем на данный момент:
+
+- «Из коробки» в С++ для работы со строками сейчас имеется `std::string`.
+- Строки подразумеваются мутабельными, что приводит к обязательному копированию всех символов строки при инициализации и
+ копировании объектов строк.
+- Соответственно, не имеем возможности быстрого копирования строк, даже если не планируем потом менять копию.
+- Конкатенация нескольких строк — задача, могущая выполнятся неоптимально, приводить к оверхеду по памяти, написать оптимальный код сложно.
+- Есть костыль для иммутабельных строк в виде `std::string_view`, однако он не решает вопросы владения строкой, поэтому по сути
+ годится только как тип для передачи параметров в функции, не меняющие строки, с оговоркой, что не может использоваться в функциях,
+ вызывающих C-API, так как не даёт гарантий нуль-терминированности.
+- Ну и к `std::string` есть вопросы, что несмотря на то, что это класс для строк, собственно для работы со строками в нём крайне куцый
+ функционал по сравнению с тем, к чему привыкли в других языках — к примеру нет замены подстрок по шаблону (в других языках это обычно
+ replace, но в С++ эта функция делает совершенно другое), trim, split, join, upper, lower и т. п.
+ Эти функции приходится каждый раз писать самому, и не факт, что у всех это получится оптимально.
+
+Надеюсь, после этого небольшого вступления вам будет более понятно, какие проблемы я решал своей строковой библиотекой и каким образом.
+
+## Библиотека simstr
+Собственно, нельзя сказать, что «я свелосипедил свою реализацию класса для строк».
+Как я ранее показал, сложно, а то и даже невозможно написать один единый строковый класс, хорошо подходящий для всех сценариев
+использования. Именно поэтому у меня не строковый класс, а строковая библиотека, которая содержит несколько разных строковых типов,
+от более простых к более сложным, каждый из которых имеет свои сильные и слабые стороны, и пользователю нужно грамотно подходить к
+вопросу, какой из этих классов в каком случае стоит использовать.
+
+Саму библиотеку я начал потихоньку разрабатывать ещё в 2011-2012 годах, когда у нас уже появилась семантика перемещения, но ещё
+не было std::string_view. Однако сейчас минимальная версия стандарта для работы библиотеки: **C++20** – используются концепты и \.
+
+Сначала я расскажу о классах библиотеки для самих строк, а потом о том, как в ней оптимально решается задача конкатенации строк.
+
+Несколько общих моментов:
+- Все классы для работы со строками шаблонизированы типом символов, но подразумевается, что символы могут быть char, char16_t,
+ char32_t, wchar_t.
+- Все строки имеют явную длину.
+- Классы владельцы строк хранят их с завершающим нулем в конце, который не входит в длину строки.
+- В самой строке могут содержаться нулевые символы, все алгоритмы работают только через длину строки, не обращая на них внимания.
+- Классы владельцы строк могут инициализироваться строками другого типа символов, выполняя конвертацию между UTF-8, UTF-16, UTF-32.
+- Для смены регистра символов и сравнения строк без учёта регистра используются встроенные таблицы для первой плоскости юникода
+ (до 0xFFFF). Строки считаются представленными в кодировке UTF-8, UTF-16, UTF-32 соответственно.
+ Однако не делается нормализация строк и не обрабатываются ситуации, когда смена регистра символа приводит к изменению их количества.
+ То есть преобразование регистра символов соответствует `std::towupper`, `std::towlower` для unicode локали,
+ только быстрее и может работать с любым видом символов.
+ Если вам нужна строгая работа с юникодом, используйте другие средства, например ICU.
+
+### Классы строк.
+
+#### Первый самый простой класс строки называется, естественно, `simple_str` :)
+(simstr::simple_str)
+
+Класс просто представляет собой указатель на начало константной строки и её длину, по сути то же самое, что `std::string_view`.
+Предназначен для работы с иммутабельными строками, не владеющий ими, то есть вы должны сами озаботиться тем, что реальная строка,
+представленная через `simple_str` – жива во время его использования.
+
+Реализует все строковые методы, не модифицирующие строку.
+
+Алиасы:
+- `ssa` для simple_str\
+- `ssu` для simple_str\
+- `ssw` для simple_str\
+- `ssuu` для simple_str\
+
+Применяется в основном для передачи строк как параметр функций, не модифицирующих переданную строку, вместо `const std::string&`,
+а также для локальных переменных при работе с частями строк.
+
+#### Второй класс — `simple_str_nt`
+(simstr::simple_str_nt)
+
+По устройству и назначению совпадает с `simple_str`, но дает гарантии нуль-терминированности строки.
+То есть если функции надо переданный параметр без изменений передать дальше как C-строку в какое то API, она должна использовать для
+параметра тип `simple_str_nt`.
+Все классы владеющих строк (simstr::sstring, simstr::lstring) могут быть преобразованы в `simple_str_nt`, так как хранят строки с завершающим нулём.
+Это позволяет писать функции с единым типом параметра, принимающим на вход любой тип владеющих строковых объектов.
+
+Алиасы:
+- `stra` для simple_str_nt\
+- `stru` для simple_str_nt\
+- `strw` для simple_str_nt\
+- `struu` для simple_str_nt\
+
+Может инициализироваться строковыми литералами:
+```cpp
+ stra text = "Text";
+```
+Длина в этом случае вычисляется сразу при компиляции. Аналогично `simple_str_nt` создается с помощью `operator""_ss`:
+
+```cpp
+ stringa result = "Count: "_ss + count;
+```
+
+#### Класс sstring (shared string).
+(simstr::sstring)
+
+Класс, умеющий хранить иммутабельную строку.
+То есть ему можно присвоить некую строку только целиком, модифицировать символы строки нельзя.
+
+Владеет строкой, управляет памятью для символов строки.
+Хранит со строками завершающий нуль, и может быть источником для `simple_str_nt`, для передачи в C-API.
+Так же, как и `simple_str`, реализует все методы, не модифицирующие строку.
+
+Алиасы:
+- `stringa` для sstring\
+- `stringu` для sstring\
+- `stringw` для sstring\
+- `stringuu` для sstring\
+
+
+То, что хранимая строка иммутабельна, позволяет применить ряд оптимизаций:
+- Для строк, не подходящих для SSO, использует общий разделяемый буфер с атомарным счётчиком ссылок.
+ Позволяет быстро копировать строку без необходимости блокировок доступа к содержимому буфера.
+- Нет необходимости хранить размер буфера (capacity) — всё равно мы ничего не дописываем в буфер.
+- Позволяет просто ссылаться на литералы программы, не копируя их символы в какой-либо буфер:
+ ```cpp
+ stringa str = "Hello!"; // Ничего не стоит, не копирует байты строки
+ stringa ltr = stra{"Hello!"}; // А вот тут копирует байты строки в ltr
+ ```
+
+Также в классе применяется **SSO** – Small String Optimization.
+Короткие строки помещаются внутри самого объекта во внутренний буфер.
+
+Размеры:
+
+Для 64 бит:
+- `stringa` – класс 24 байта, SSO до 23 символов.
+- `stringu` – класс 32 байта, SSO до 15 символов.
+- `stringuu` – класс 32 байта, SSO до 7 символов.
+
+Для 32 бит:
+- `stringa` – класс 16 байт, SSO до 15 символов.
+- `stringu` – класс 24 байта, SSO до 11 символов.
+- `stringuu` – класс 24 байта, SSO до 5 символов.
+
+#### Класс lstring (local string)
+(simstr::lstring)
+
+Класс, хранящий строку и позволяющий её модифицировать.
+Владеет строкой, управляет памятью для символов строки.
+Хранит со строками завершающий нуль, и может быть источником для `simple_str_nt`, для передачи в C-API.
+Как и все остальные классы, реализует все методы, не модифицирующие строку.
+
+В качестве `N` в параметре шаблона задаётся размер внутреннего буфера для хранения символов.
+Строки длиной до N символов хранятся внутри объекта, а при превышении этого количества — аллоцируется динамический буфер,
+в который сохраняются символы. При копировании объекта все символы также всегда копируются.
+
+Если `forShare` == true и символы не помещаются в локальный буфер, то динамический буфер создается с дополнительным местом,
+так чтобы совпадать по структуре с буфером `sstring`. Тогда при перемещении `lstring` в `sstring` – переместится только указатель
+на буфер, без излишнего копирования символов.
+
+Этот класс удобен для работы со строками как локальная переменная на стеке.
+Обычно мы предполагаем примерный размер строк, с котороми будем работать, и можем создать локальную строку с буфером на стеке,
+и работать с ней. При этом не опасаясь переполнения буфера, так как в этом случае строка переключится на динамический буфер.
+
+Алиасы:
+- `lstringa` для lsrting\
+- `lstringu` для lsrting\
+- `lstringw` для lsrting\
+- `lstringuu` для lsrting\
+- `lstringsa` для lsrting\
+- `lstringsu` для lsrting\
+- `lstringsw` для lsrting\
+- `lstringsuu` для lsrting\
+
+
+Небольшой пример использования с пояснениями:
+```cpp
+ #ifdef _WIN32
+ const char path_separator = '\\';
+ #else
+ const size_t MAX_PATH = 260;
+ const char path_separator = '/';
+ #endif
+
+ auto get_current_dir() {
+ #ifdef _WIN32
+ /* заполняем буфер wchar_t строки lstringw из GetCurrentDirectoryW с возможным
+ увеличением буфера и конвертируем в ut8 char. В конструкторе используется то, что появилось
+ только в С++23 как `resize_and_overwrite`, а у нас было изначально :) */
+
+ lstringa path{lstringw{ [](auto p, auto s) { return GetCurrentDirectoryW(DWORD(s + 1), p); }}};
+
+ /* Эта одна строчка делает примерно то же самое, что и вот такой код.
+ typedef struct lstringa_MAX_PATH_t {
+ char* data;
+ size_t length;
+ size_t capacity;
+ char local_buffer[MAX_PATH + 1];
+ } lstringa_MAX_PATH;
+
+ lstringa_MAX_PATH* get_current_dir(lstringa_MAX_PATH* result) {
+ wchar_t buffer[MAX_PATH + 1], *buf = buffer;
+ DWORD size = sizeof(buffer) / sizeof(wchar_t), lengthOfpath;
+ for (;;) {
+ // Возвращает либо количество скопированных символов без учёта завершающего нуля,
+ // либо если буфер мал, то нужный размер буфера вместе с завершающим нулём
+ DWORD ret = GetCurrentDirectoryW(size, buf);
+ if (ret < size) {
+ // Влезло в буфер, хотя в Windows пути могут быть и длиннее, чем MAX_PATH, если начинаются с \\?\
+ // https://learn.microsoft.com/ru-ru/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
+ lenOfpath = ret;
+ break;
+ }
+ size = ret;
+ if (buf != buffer)
+ free(buf);
+ buf = malloc(size);
+ }
+ utf16toUtf8(buf, lengthOfPath, result);
+ if (buf != buffer)
+ free(buf);
+ return result;
+ }
+ */
+ #else
+ lstringa path{ [](char* p, size_t s) {
+ const char* res = getcwd(p, s + 1);
+ if (res) {
+ return stra{res}.length(); // Возвращаем длину строки
+ }
+ if (errno == ERANGE) // Не влезло в буфер, попробуем в два раза больше
+ return s * 2;
+ return 0ul;
+ }};
+ #endif
+ // Удостоверимся, что строка будет заканчиваться разделителем директорий
+ if (!path.length() || path.at(-1) != path_separator) {
+ path += e_c(1, path_separator);
+ }
+ return path;
+ }
+
+ stringa build_full_path(ssa fileName) {
+ return get_current_dir() + fileName + ".txt";
+ /*
+ Здесь сначала на стеке создастся временный объект lstringa для вызова get_current_dir.
+ Функция get_current_dir заполнит его названием текущего каталога.
+ В 99.9% случаев для этого хватит локального буфера на стеке.
+ После рассчитывается общая длина для результата: длина current_dir + длина fileName + 4.
+ Определяется буфер для строки конечного результата - если длина меньше 24 — строка будет размещена прямо в stringa,
+ иначе аллоцируется буфер для результирующей строки сразу нужного размера.
+ Затем в буфер результирующей строки последовательно копируются символы из current_dir, file_name, ".txt";
+ Ну и благодаря RVO - место для самого результата (stringa) - отводится в вызывающей функции,
+ то есть никакого дополнительного копирования при возврате не будет.
+
+ Таким образом, будет максимум всего две аллокации памяти (если current_dir не влезет в MAX_PATH),
+ или одна, если результирующая строка длиннее 23 символов, при этом эта аллокация будет сразу нужного размера.
+ */
+ }
+```
+
+В этом примере вы наверняка заметили, как конкатенируются строки и задались вопросом — как же при двух сложениях считалась
+длина всего результата, чтобы выделить необходимое место сразу за один раз, без промежуточных буферов?
+
+Ответ на этот вопрос:
+
+### Строковые выражения
+Дело в том, что в библиотеке нет сложения строковых объектов как такового. Сложение выполняется для «строковых выражений».
+
+*Строковое выражение* — это любой объект произвольного типа, имеющий функции `length` и `place`.
+Функция `length` – возвращает длину строки, функция `place` – помещает символы строки в переданный ей буфер.
+
+Любая владеющая строка (simstr::sstring, simstr::lstring) может инициализироваться строковым выражением — она запрашивает у него длину,
+выделяет место для хранения символов, и передает это место строковому выражению, вызывая его функцию place.
+
+Для строковых выражений определена шаблонная функция сложения:
+```cpp
+ template B>
+ inline auto operator + (const A& a, const B& b) {
+ return strexprjoin{a, b};
+ }
+```
+
+`strexprjoin` – шаблонный тип, который сам является строковым выражением.
+В себе он хранит ссылки на два переданных ему строковых выражения.
+При запросе длины он выдает сумму длин двух строковых выражений, а при размещении символов — сначала размещает
+в переданном буфере первое выражение, затем второе.
+```cpp
+ template B>
+ struct strexprjoin {
+ using symb_type = typename A::symb_type;
+ const A& a;
+ const B& b;
+ constexpr strexprjoin(const A& a_, const B& b_) : a(a_), b(b_){}
+ constexpr size_t length() const noexcept { return a.length() + b.length(); }
+ constexpr symb_type* place(symb_type* p) const noexcept { return b.place(a.place(p)); }
+ };
+```
+Таким образом, операция сложения строковых выражений создает объект, также являющийся строковым выражением,
+к которому также может быть применена следующая операция сложения, и который рекурсивно хранит ссылки на слагаемые части,
+каждая из которых знает свой размер и умеет размещать себя в буфере результата. И так далее, к каждому получаемому
+строковому выражению можно снова применить `operator +`, формируя цепочку из нескольких строковых выражений,
+и в итоге "материализовать" последний получившийся объект, который сначала посчитает размер всей общей памяти для
+конечного результата, а затем разместит вложенные подвыражения в один буфер.
+
+Все строковые типы библиотеки сами являются строковыми выражениями, то есть могут служить слагаемыми в конкатенациях
+строковых выражений.
+
+Также `operator+` определён для строковых выражений и строковых литералов, строковых выражений и чисел (числа конвертируются
+в десятичное представление), а также вы можете сами добавить желаемые типы.
+
+Пример:
+```cpp
+ stringa text = header + " count=" + count + ", done";
+```
+
+Существует несколько типов строковых выражений "из коробки", для выполнения различных операций со строками:
+
+#### expr_spaces<ТипСимвола, КоличествоСимволов, Символ = ' '>{}
+Выдает строку длиной КоличествоСимволов, заполненную заданным символом. Количество символов и символ - константы времени
+компиляции. Для некоторых случаев есть сокращенная запись:
+
+ e_spca(КоличествоСимволов) - строка char пробелов
+ e_spcw(КоличествоСимволов) - строка w_char пробелов
+
+#### expr_pad<ТипСимвола>{КоличествоСимволов, Символ = ' '}
+Выдает строку длиной КоличествоСимволов, заполненную заданным символом.
+Количество символов и символ могут задаваться в рантайме. Сокращенная запись:
+
+ e_c(КоличествоСимволов, Символ)
+
+#### e_choice(bool Condition, StrExpr1, StrExpr2)
+Если Condition == true, результат будет равен StrExpr1, иначе StrExpr2.
+
+#### e_if(bool Condition, StrExpr1)
+Если Condition == true, результат будет равен StrExpr1, иначе пустая строка.
+
+#### expr_num<ТипСимвола>(ЦелоеЧисло)
+Конвертирует число в десятичное представление. Редко используется, так как для строковых выражений и чисел
+переопределен оператор "+", и число можно просто написать как `text + number`;
+
+#### expr_real<ТипСимвола>(ВещественноеЧисло)
+конвертирует число в десятичное представление. Редко используется, так как для строковых выражений и чисел
+переопределен оператор "+", и число можно просто написать как `text + number`;
+
+#### e_join(контейнер, "Разделитель")
+Конкатенирует все строки в контейнере, используя разделитель. Если ПослеПоследнего == true,
+то разделитель добавляется и после последнего элемента контейнера, иначе только между элементами.
+Если ТолькоНеПустые == true, то пустые строки пропускаются без добавления разделителя.
+
+#### e_repl(ИсходнаяСтрока, "Искать", "Заменять")
+Заменяет в исходной строке вхождения "Искать" на "Заменять".
+Шаблоны поиска и замены - строковые литералы времени компиляции.
+
+#### expr_replaced<ТипСимвола>{ИсходнаяСтрока, Искать, Заменять}
+Заменяет в исходной строке вхождения Искать на Заменять.
+Шаблоны поиска и замены - могут быть любыми строковыми объектами в рантайме.
+
+#### empty_expr<ТипСимвола>
+Выдает пустую строку. Сокращённая запись — eea, eeu, eew, eeuu. Применяется если формирование строки начинается с числа и строкового литерала:
+```cpp
+ str = eea + count + " times.";
+```
+так как оператор сложения определён только для сложения строкового выражения и числа.
+Также замечу, что существует `operator""_ss`, который превращает строковый литерал в объект `simple_str_nt`, который уже является строковым выражением:
+```cpp
+ str = "Count = "_ss + count;
+ ...
+ str = count + " times."_ss;
+```
+
+#### Свои строковые выражения
+Вы можете сами создавать свои типы строковых выражений для оптимального формирования строк в нужных вам целях и алгоритмах.
+Для этого просто создайте тип с методами `length`, `place` и `typename symb_type`.
+Примеры создания и использования из реальных проектов:
+
+```cpp
+/* Сформировать строку в JSON формате, в 16 битных символах */
+struct expr_json_str {
+ using symb_type = u16s;
+ ssu text;
+ size_t l;
+ size_t length() const noexcept {
+ return l;
+ }
+ u16s* place(u16s* ptr) const noexcept;
+ expr_json_str(ssu t);
+};
+
+inline expr_json_str::expr_json_str(ssu t) : text(t) {
+ const u16s* ptr = text.symbols();
+ size_t add = 0;
+
+ for (size_t i = 0; i < text.length(); i++) {
+ switch (*ptr++) {
+ case '\b':
+ case '\f':
+ case '\r':
+ case '\n':
+ case '\t':
+ case '\"':
+ case '\\':
+ add++;
+ }
+ }
+ l = text.len + add;
+}
+
+inline u16s* expr_json_str::place(u16s* ptr) const noexcept {
+ const u16s *r = text.symbols();
+ size_t lenOfText = text.length(), lenOfTail = l;
+ while (lenOfTail > lenOfText) {
+ u16s s = *r++;
+ switch (s) {
+ case '\b':
+ *ptr++ = '\\';
+ *ptr++ = 'b';
+ lenOfTail--;
+ break;
+ case '\f':
+ *ptr++ = '\\';
+ *ptr++ = 'f';
+ lenOfTail--;
+ break;
+ case '\r':
+ *ptr++ = '\\';
+ *ptr++ = 'r';
+ lenOfTail--;
+ break;
+ case '\n':
+ *ptr++ = '\\';
+ *ptr++ = 'n';
+ lenOfTail--;
+ break;
+ case '\t':
+ *ptr++ = '\\';
+ *ptr++ = 't';
+ lenOfTail--;
+ break;
+ case '\"':
+ *ptr++ = '\\';
+ *ptr++ = '\"';
+ lenOfTail--;
+ break;
+ case '\\':
+ *ptr++ = '\\';
+ *ptr++ = '\\';
+ lenOfTail--;
+ break;
+ default:
+ *ptr++ = s;
+ break;
+ }
+ lenOfTail--;
+ lenOfText--;
+ }
+ if (lenOfTail) {
+ std::char_traits::copy(ptr, r, lenOfTail);
+ ptr += lenOfTail;
+ }
+ return ptr;
+}
+```
+
+Использование:
+
+```cpp
+........
+chunked_string_builder vtText;
+........
+vtText << uR"({"#type":"jxs:string","#value":")" + expr_json_str(name) + u"\"}";
+.......
+```
+
+Ещё пример
+```cpp
+/* Нужно сформировать бинарные данные в BASE64 формате, в 16 битных символах */
+struct expr_str_base64 {
+ using symb_type = u16s;
+ ssa text;
+ size_t length() const noexcept {
+ return (text.len + 2) / 3 * 4;
+ }
+ u16s* place(u16s* ptr) const noexcept;
+ expr_str_base64(ssa t) : text(t) {}
+};
+
+inline u16s* expr_str_base64::place(u16s* ptr) const noexcept {
+ static constexpr u8s alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ const unsigned char* t = (const unsigned char*)text.str;
+
+ size_t i = 0;
+ if (text.len > 2) {
+ for (; i < text.len - 2; i += 3) {
+ *ptr++ = alphabet[(t[i] >> 2) & 0x3F];
+ *ptr++ = alphabet[((t[i] & 0x3) << 4) | ((int)(t[i + 1] & 0xF0) >> 4)];
+ *ptr++ = alphabet[((t[i + 1] & 0xF) << 2) | ((int)(t[i + 2] & 0xC0) >> 6)];
+ *ptr++ = alphabet[t[i + 2] & 0x3F];
+ }
+ }
+
+ if (i < text.len) {
+ *ptr++ = alphabet[(t[i] >> 2) & 0x3F];
+ if (i == (text.len - 1)) {
+ *ptr++ = alphabet[((t[i] & 0x3) << 4)];
+ *ptr++ = '=';
+ } else {
+ *ptr++ = alphabet[((t[i] & 0x3) << 4) | ((int)(t[i + 1] & 0xF0) >> 4)];
+ *ptr++ = alphabet[((t[i + 1] & 0xF) << 2)];
+ }
+ *ptr++ = '=';
+ }
+ return ptr;
+}
+```
+
+Использование:
+```cpp
+......
+chunked_string_builder vtText;
+......
+vtText << u"{\"#\",87126200-3e98-44e0-b931-ccb1d7edc497,{1,{#base64:" + expr_str_base64(v) + u"}}},";
+......
+```
+
+И ещё
+
+```cpp
+/* Нужно преобразовать tm в строку даты/времени в 16-битных символах */
+struct expr_str_tm {
+ using symb_type = u16s;
+ const tm& t;
+ size_t length() const noexcept {
+ return 19;
+ }
+ u16s* place(u16s* ptr) const noexcept;
+ expr_str_tm(const tm& _t) : t(_t) {}
+};
+
+inline u16s* expr_str_tm::place(u16s* ptr) const noexcept {
+ if constexpr (sizeof(wchar_t) == 2) {
+ // Под Windows можно сразу форматнуть строку в нужный буфер
+ std::swprintf((wchar_t*)ptr, 20, L"%04i-%02i-%02i %02i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
+ t.tm_hour, t.tm_min, t.tm_sec);
+ } else {
+ // Сначала форматнём в промежуточный буфер, потом скопируем в результат
+ char buf[20];
+ std::snprintf(buf, 20, "%04i-%02i-%02i %02i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
+ t.tm_min, t.tm_sec);
+ for (unsigned i = 0; i < 19; i++) {
+ ptr[i] = buf[i];
+ }
+ }
+ return ptr + 19;
+}
+```
+
+Использование
+
+```cpp
+......
+bool makeBind(SqliteQuery& query, tVariant& param, unsigned paramNum) {
+ switch (param.vt) {
+......
+ case VTYPE_DATE:
+ query.bind(paramNum, lstringu<30>{expr_str_tm{winDateToTm(param.date)}}.to_str());
+ break;
+ case VTYPE_TM:
+ query.bind(paramNum, lstringu<30>{expr_str_tm{param.tmVal}}.to_str());
+ break;
+......
+```
+
+ВНИМАНИЕ: обычно поля в объектах строковых выражений являются ссылками на исходные данные.
+И ссылки эти почти всегда ведут на локальные или временные объекты. Поэтому крайне рискованно возвращать строковые выражения
+из функций — надо сто раз проверить, что в них не попали ссылки на локальные или временные переменные.
+Возьмите за правило — можно легко передавать строковые выражения в функции, и опасно возвращать их из функций.
+Лучше при возврате материализовать строковое выражение в строковый объект, содержащий итоговую строку.
+При желании тип возвращаемой строки можно задать шаблонным параметром.
+
+
+### Класс chunked_string_builder
+
+Предназначен для конкатенации множества строк.
+Когда вам нужно последовательно формировать длинный текст из множества небольших кусочков (например, формируете html ответ
+и т. п.) - последовательно складывать всё в один строковый объект крайне неоптимально — будет много переаллокаций и
+перекопирования уже накопленных символов. В этом случае удобно использовать chunked_string_builder — всё, что он умеет,
+это прибавлять строку к накопленным символам. Однако делает он это не в единый последовательный буфер памяти, а в отдельные
+буфера, не меньше чем заданное выравнивание. При заполнении очередного буфера он просто создает ещё один буфер и продолжает
+складывать данные в него.
+
+То есть допустим вы задали выравнивание 1024.
+Добавили несколько строк, заполнили буфер на 100 символов. И добавляете строку длинной 3000 символов.
+При этом 924 символа скопируются в первый буфер, заполнив его до конца.
+Для оставшихся 2076 создастся буфер размером 3072 символа, и они скопируются в него, в нём останется место для 996 символов.
+Так последовательно каждый буфер заполняется до конца, и имеет размер кратный заданному выравниванию.
+Таким образом избегаются переаллокации и перекопирование обработанных символов.
+
+После окончательного заполнения вы можете работать с накопленными данными — либо слить все буфера в одну последовательную
+строку (размер для буфера которой вы теперь уже знаете), либо перебирать их по отдельности, например, посылая эти буфера
+в сеть. Либо последовательно копируя данные в буфер заданного размера.
diff --git a/for_debug/readme.md b/for_debug/readme.md
index a25de36..b754951 100644
--- a/for_debug/readme.md
+++ b/for_debug/readme.md
@@ -1,14 +1,16 @@
-# Объекты simstr в отладчиках
-Для более удобного отображения объектов simstr в отладчиках msvc и gdb подготовлено два файла:
-simstr.natvis - для использования в отладчике MSVC, и simstr_pretty_print.py для работы с gdb.
+# simstr Objects in Debuggers
+[On Russian|По-русски](readme_ru.md)
-# Объекты simstr в отладчиках
-Если вы работаете в MS Visual Studio simstr.natvis автоматически добавляется в pdb файл,
-и обеспечивает удобный просмотр строковых объектов simstr везде, где используется эта библиотека.
+Two files have been prepared for more convenient display of simstr objects in msvc and gdb debuggers:
+simstr.natvis - for use in the MSVC debugger, and simstr_pretty_print.py for working with gdb.
-# Объекты simstr в Visual Studio Code
-## При работе в gdb
-В конфигурации отладчика необходимо добавить следующие строки:
+# simstr Objects in Debuggers
+If you are working in MS Visual Studio, simstr.natvis is automatically added to the pdb file,
+and provides convenient viewing of simstr string objects wherever this library is used.
+
+# simstr Objects in Visual Studio Code
+## When working in gdb
+In the debugger configuration, you need to add the following lines:
```
"setupCommands": [
@@ -25,16 +27,15 @@ simstr.natvis - для использования в отладчике MSVC, и
]
```
-После этого в отладчике будут удобно отображаться объекты simstr.
-Скрипт инспектирует переменные этих типов и выдает для них текстовое описание, отображающее их
-содержимое в удобном виде. В первой строке отображается основная информация, видимая в окне
-инспектирования переменных. При наведении указателя мыши на переменную в окне исходного кода
-или на значении в окне инспектирования переменных во всплывающем тултипе будет показана
-остальная информация.
+After that, simstr objects will be conveniently displayed in the debugger.
+The script inspects variables of these types and provides a textual description for them, displaying their
+content in a convenient form. The first line displays the basic information visible in the
+variable inspection window. When you hover the mouse over a variable in the source code window
+or over a value in the variable inspection window, the remaining information will be shown in the pop-up tooltip.
-Конфигурации отладчика располагаются в файле `.vscode/launch.json`.
-Возможно, у вас также установлено расширение `CMake Tools`, которое позволяет выбирать целевой проект для запуска.
-В этом случае настройки для подключения скрипта прописываются в `.vscode/settings.json`:
+Debugger configurations are located in the `.vscode/launch.json` file.
+You may also have the `CMake Tools` extension installed, which allows you to select the target project to run.
+In this case, the settings for connecting the script are written in `.vscode/settings.json`:
```
"cmake.debugConfig": {
@@ -55,7 +56,7 @@ simstr.natvis - для использования в отладчике MSVC, и
}
```
-Если же в Visual Studio Code вы работаете с отладчиком MSVC, то есть в launch.json `"type": "cppvsdbg"` то настройка другая:
+If you are working with the MSVC debugger in Visual Studio Code, that is, in launch.json `"type": "cppvsdbg"`, then the setting is different:
```
"configurations": [
{
diff --git a/for_debug/readme_ru.md b/for_debug/readme_ru.md
new file mode 100644
index 0000000..2960da3
--- /dev/null
+++ b/for_debug/readme_ru.md
@@ -0,0 +1,70 @@
+# Объекты simstr в отладчиках
+[On English|По-английски](readme.md)
+
+Для более удобного отображения объектов simstr в отладчиках msvc и gdb подготовлено два файла:
+simstr.natvis - для использования в отладчике MSVC, и simstr_pretty_print.py для работы с gdb.
+
+# Объекты simstr в отладчиках
+Если вы работаете в MS Visual Studio simstr.natvis автоматически добавляется в pdb файл,
+и обеспечивает удобный просмотр строковых объектов simstr везде, где используется эта библиотека.
+
+# Объекты simstr в Visual Studio Code
+## При работе в gdb
+В конфигурации отладчика необходимо добавить следующие строки:
+
+```
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ },
+ {
+ "description": "Enable pretty-printing for simstr",
+ "text": "source ${workspaceFolder}/for_debug/simstr_pretty_print.py",
+ "ignoreFailures": true
+ }
+ ]
+```
+
+После этого в отладчике будут удобно отображаться объекты simstr.
+Скрипт инспектирует переменные этих типов и выдает для них текстовое описание, отображающее их
+содержимое в удобном виде. В первой строке отображается основная информация, видимая в окне
+инспектирования переменных. При наведении указателя мыши на переменную в окне исходного кода
+или на значении в окне инспектирования переменных во всплывающем тултипе будет показана
+остальная информация.
+
+Конфигурации отладчика располагаются в файле `.vscode/launch.json`.
+Возможно, у вас также установлено расширение `CMake Tools`, которое позволяет выбирать целевой проект для запуска.
+В этом случае настройки для подключения скрипта прописываются в `.vscode/settings.json`:
+
+```
+ "cmake.debugConfig": {
+ "MIMode": "gdb",
+ "environment": [],
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ },
+ {
+ "text": "source ${workspaceFolder}/for_debug/simstr_pretty_print.py",
+ "description": "pretty print simstr",
+ "ignoreFailures": true
+ }
+ ]
+ }
+```
+
+Если же в Visual Studio Code вы работаете с отладчиком MSVC, то есть в launch.json `"type": "cppvsdbg"` то настройка другая:
+```
+ "configurations": [
+ {
+ .....
+ "type": "cppvsdbg",
+ ....
+ "visualizerFile": "${workspaceFolder}/for_debug/simstr.natvis"
+ },
+ ...
+```
diff --git a/include/simstr/strexpr.h b/include/simstr/strexpr.h
index 9892a77..678a4fc 100644
--- a/include/simstr/strexpr.h
+++ b/include/simstr/strexpr.h
@@ -12,7 +12,8 @@
#include
/*!
- * @brief Пространство имён для объектов библиотеки
+ * @ru @brief Пространство имён для объектов библиотеки
+ * @en @brief Library namespace
*/
namespace simstr {
@@ -145,15 +146,25 @@ public:
};
/*!
- * @brief Базовая концепция строкового объекта.
- * @tparam A - проверяемый тип
- * @tparam K - тип символов
- * @details В библиотеке для разных целей могут использоваться различные типы объектов строк.
+ * @ru @brief Базовая концепция строкового объекта.
+ * @ru @tparam A - проверяемый тип
+ * @ru @tparam K - тип символов
+ * @ru @details В библиотеке для разных целей могут использоваться различные типы объектов строк.
* Мы считаем строковым объектом любой объект, поддерживающий методы:
* - `is_empty()`: возвращает, пуста ли строка.
* - `length()`: возвращает длину строки без нулевого терминатора.
* - `symbols()`: возвращает указатель на строку символов.
* - `typename symb_type`: задаёт тип символов строки
+ *
+ * @en @brief Base concept of string object.
+ * @en @tparam A - tested type
+ * @en @tparam K - type of symbols @ru K - тип символов
+ * @en @details The library can use different types of string objects for different purposes.
+ * We consider a string object to be any object that supports methods:
+ * - `is_empty()`: Returns whether the string is empty.
+ * - `length()`: returns the length of a string without a null terminator.
+ * - `symbols()`: returns a pointer to a string of symbols.
+ * - `typename symb_type`: sets the character type of the string
*/
template
concept StrType = requires(const A& a) {
diff --git a/readme.md b/readme.md
index ee5ceef..f56beef 100644
--- a/readme.md
+++ b/readme.md
@@ -1,94 +1,96 @@
-# simstr - библиотека строковых объектов и функций
+# simstr - String object and function library
[](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml)
-Версия 1.2.4.
+Version 1.2.4.
-В этой библиотеке содержится реализация нескольких видов строковых объектов и различных алгоритмов для работы со строками.
+[On Russian|По-русски](readme_ru.md)
-Цель библиотеки - сделать работу со строками в С++ такой же простой и лёгкой, как во множестве других языков, особенно
-скриптовых, но при этом сохранив оптимальность и производительность на уровне С и C++, и даже улучшив их.
+This library contains the implementation of several types of string objects and various algorithms for working with strings.
-Не секрет, что работа со строками в С++ зачастую доставляет боль. Класс `std::string` часто неудобен либо неэффективен.
-Многих функций, обычно необходимых при работе со строками, просто нет, и их каждому приходится писать самому.
+The goal of the library is to make working with strings in C++ as simple and easy as in many other languages, especially
+scripting languages, while maintaining optimality and performance at the level of C and C++, and even improving them.
-Эта библиотека не делалась как универсальный комбайн, который "может всё", я реализовывал то, что мне приходилось
-использовать в работе, стараясь сделать это наиболее эффективным способом, и скромно надеюсь, что кое-что у меня получилось
-и пригодится другим людям, либо напрямую, либо как источник идей.
+It's no secret that working with strings in C++ often causes pain. The `std::string` class is often inconvenient or inefficient.
+Many functions that are usually necessary when working with strings are simply not there, and everyone has to write them themselves.
-Библиотека не претендует на роль "поменял хедер и всё заработало лучше". Многие методы я старался делать совместимыми
-с `std::string` и `std::string_view`, но особо с этим не заморачивался. Переписывание старого кода на работу с simstr
-потребует некоторых усилий, но уверяю, что они окупятся. А новый код писать с её применением легко и доставляет удовольствие :)
+This library was not made as a universal combine that "can do everything", I implemented what I had to
+use at work, trying to do it in the most efficient way, and I modestly hope that I succeeded in something
+and will be useful to other people, either directly or as a source of ideas.
-Основное отличие simstr от std::string - для работы со строками используется не единый универсальный класс, а несколько
-видов объектов, каждый из которых хорош для своих целей, и при этом хорошо взаимодействующих друг с другом.
-Если вы активно использовали std::string_view и понимали, в чём его преимущество и недостатки по сравнению с std::string,
-то подход simstr вам также будет понятен.
+The library does not pretend to be a "change the header and everything works better" solution. I tried to make many methods compatible
+with `std::string` and `std::string_view`, but I didn't bother with it much. Rewriting old code to work with simstr
+will require some effort, but I assure you that it will pay off. And writing new code with its use is easy and enjoyable :)
-## Основные возможности библиотеки
-- Строки `char`, `char16_t`, `char32_t`, `wchar_t`.
-- Прозрачное преобразование строк из одного типа символов в другой, с автоматической конвертацией между UTF-8, UTF-16, UTF-32,
- используя [simdutf](https://github.com/simdutf/simdutf).
-- Расширяемая система "Строковых выражений". Позволяет эффективно реализовать преобразование и сложение (конкатенацию) строк, литералов,
- чисел и возможно других объектов.
-- Строковые функции:
- - Получение подстрок.
- - Поиск подстрок и символов - с начала или с конца строки.
- - Различный тримминг строк - справа, слева, везде, по пробельным символам, по заданным символам.
- - Замена подстрок.
- - Замена набора символов на набор соответствующих подстрок.
- - Слияние (join) контейнеров строк в единую строку, с заданием разделителей и опций - "пропускать пустые", "разделитель после последней".
- - Разбиение (split) строк на части по заданному разделителю. Разбиение возможно сразу в контейнер со строками, либо вызовом функтора для
- каждой подстроки, либо путем итерации с помощью итератора `Splitter`.
-- Интеграция с функциями форматирования `format` и `sprintf` (с автоматическим увеличением буфера).
- Форматирование возможно для строк `char`, `wchar_t` и строк, совместимых с `wchar_t` по размеру.
- То есть под Windows это `char16_t`, под Linux - `char32_t`. Писать свою библиотеку форматирования не входило в мои замыслы.
-- Парсинг целых чисел с возможностью "тонкой" настройки при компиляции - можно задавать опции проверки переполнения,
- пропуск пробельных символов, конкретное основание счисления либо автовыбор по префиксам `0x`, `0`, `0b`, `0o`,
- допустимость знака `+`. Парсинг реализован для всех видов строк и символов.
-- Парсинг double пока реализован вызовом стандартной библиотеки и работает только для строк `char`, `wchar_t` и совместимых с
- `wchar_t` по размеру типов.
-- Содержится минимальная поддержка Unicode при преобразовании `upper`, `lower` и регистро-независимом сравнении строк.
- Работает только для символов первой плоскости Unicode (до 0xFFFF), а при смене регистра не учитываются случаи, когда один code point
- может преобразовываться в несколько, то есть преобразование регистра символов соответствует `std::towupper`, `std::towlower` для unicode локали, только быстрее и может работать с любым видом символов.
-- Реализован `hash map` для ключей строкового типа, на базе `std::unordered_map`, с возможностью более эффективного хранения и
- сравнения ключей по сравнению с ключами `std::string`. Поддерживается возможность регистро-независимого сравнения ключей (Ascii или
- минимальный Unicode (см. предыдущий пункт)).
+The main difference between simstr and std::string is that instead of a single universal class, several
+types of objects are used to work with strings, each of which is good for its own purposes, and at the same time interacts well with each other.
+If you actively used std::string_view and understood its advantages and disadvantages compared to std::string,
+then the simstr approach will also be clear to you.
-## Основные объекты библиотеки
-- simple_str<K> - самая простая строка (или кусок строки), иммутабельная, не владеющая, аналог `std::string_view`.
-- simple_str_nt<K> - то же самое, только заявляет, что заканчивается 0. Для работы со сторонними C-API.
-- sstring<K> - shared string, иммутабельная, владеющая, с разделяемым буфером символов, поддержка SSO.
-- lstring<K, N> - local string, мутабельная, владеющая, с задаваемым размером SSO буфера.
+## Main features of the library
+- Strings `char`, `char16_t`, `char32_t`, `wchar_t`.
+- Transparent conversion of strings from one character type to another, with automatic conversion between UTF-8, UTF-16, UTF-32,
+ using [simdutf](https://github.com/simdutf/simdutf).
+- Extensible "String Expression" system. Allows you to efficiently implement the conversion and addition (concatenation) of strings, literals,
+ numbers and possibly other objects.
+- String functions:
+ - Getting substrings.
+ - Searching for substrings and characters - from the beginning or from the end of the string.
+ - Various string trimming - right, left, everywhere, by whitespace characters, by specified characters.
+ - Replacing substrings.
+ - Replacing a set of characters with a set of corresponding substrings.
+ - Merging (join) containers of strings into a single string, with specifying separators and options - "skip empty", "separator after last".
+ - Splitting strings into parts by a specified separator. Splitting is possible directly into a container with strings, or by calling a functor for
+ each substring, or by iterating using the `Splitter` iterator.
+- Integration with `format` and `sprintf` formatting functions (with automatic buffer increase).
+ Formatting is possible for `char`, `wchar_t` strings and strings compatible with `wchar_t` in size.
+ That is, under Windows it is `char16_t`, under Linux - `char32_t`. Writing my own formatting library was not part of my plans.
+- Parsing integers with the possibility of "fine" tuning during compilation - you can set options for checking overflow,
+ skipping whitespace characters, a specific radix or auto-selection by prefixes `0x`, `0`, `0b`, `0o`,
+ admissibility of the `+` sign. Parsing is implemented for all types of strings and characters.
+- Parsing double is currently implemented by calling the standard library and only works for `char`, `wchar_t` strings and types compatible with
+ `wchar_t` in size.
+- Minimal Unicode support is included when converting `upper`, `lower` and case-insensitive string comparison.
+ It only works for characters in the first plane of Unicode (up to 0xFFFF), and when changing case, it does not take into account cases where one code point
+ can be converted into several, that is, the case conversion of characters corresponds to `std::towupper`, `std::towlower` for the unicode locale, only faster and can work with any type of characters.
+- Implemented `hash map` for string type keys, based on `std::unordered_map`, with the possibility of more efficient storage and
+ comparison of keys compared to `std::string` keys. Case-insensitive key comparison is supported (Ascii or
+ minimal Unicode (see previous paragraph)).
-## Статьи
-- [Обзор и введение](docs/overview.md)
-- [Обзорная статья на Хабре](https://habr.com/ru/articles/935590)
-- [Описание применяемой техники "Expression Templates"](https://habr.com/ru/articles/936468/)
+## Main objects of the library
+- simple_str<K> - the simplest string (or piece of string), immutable, not owning, analogue of `std::string_view`.
+- simple_str_nt<K> - the same, only declares that it ends with 0. For working with third-party C-API.
+- sstring<K> - shared string, immutable, owning, with shared character buffer, SSO support.
+- lstring<K, N> - local string, mutable, owning, with a specified size of the SSO buffer.
-## Использование
-`simstr` состоит из трёх заголовочных файлов и двух исходников. Можно подключать как CMake проект через `add_subdirectory` (библиотека `simstr`),
-можно просто включить файлы в свой проект. Для сборки также требуется [simdutf](https://github.com/simdutf/simdutf) (при использовании CMake
-скачивается автоматически).
+## Articles
+- [Overview and introduction](docs/overview.md)
+- [Overview article on Habr](https://habr.com/ru/articles/935590)
+- [Description of the "Expression Templates" technique used](https://habr.com/ru/articles/936468/)
-Для работы `simstr` требуется компилятор стандарта не ниже С++20 - используются концепты и std::format.
-Работа проверялась под Windows на MSVC-19 и Clang-19, под Linux - на GCC-13 и Clang-21.
-Также проверялась работа в WASM, сборка в Emscripten 4.0.6, Clang-21.
+## Usage
+`simstr` consists of three header files and two source files. You can connect as a CMake project via `add_subdirectory` (the `simstr` library),
+you can simply include the files in your project. Building also requires [simdutf](https://github.com/simdutf/simdutf) (when using CMake
+it is downloaded automatically).
+
+`simstr` requires a compiler of standard no lower than C++20 to work - concepts and std::format are used.
+The work was tested under Windows on MSVC-19 and Clang-19, under Linux - on GCC-13 and Clang-21.
+The work in WASM was also tested, built in Emscripten 4.0.6, Clang-21.
-## Бенчмарки
-Бенчмарки производятся с использованием фреймворка [Google benchmark](https://github.com/google/benchmark).
-Постарался сделать замеры для наиболее типичных операций, встречающихся в обычной работе. Я проводил замеры на своём оборудовании, под
-Windows и Linux (в WSL), с использованием компиляторов MSVC, Clang, GCC. Сторонние результаты приветствуются.
-Также проводил замеры в WASM, сборка в Emscripten. Обращаю внимание, что под WASM в Emscripten собирается 32-битная сборка, а значит,
-размеры буферов SSO в объектах меньше.
+## Benchmarks
+Benchmarks are performed using the [Google benchmark](https://github.com/google/benchmark) framework.
+I tried to make measurements for the most typical operations that occur in normal work. I took measurements on my equipment, under
+Windows and Linux (in WSL), using MSVC, Clang, GCC compilers. Third-party results are welcome.
+I also took measurements in WASM, built in Emscripten. I draw your attention to the fact that a 32-bit build is assembled under WASM in Emscripten, which means that
+the sizes of SSO buffers in objects are smaller.
-- [Исходный код бенчмарков](bench/bench_str.cpp)
-- [Результаты бенчмарков](https://snegopat.ru/simstr/results.html)
+- [Benchmark source code](bench/bench_str.cpp)
+- [Benchmark results](https://snegopat.ru/simstr/results.html)
-## Примеры использования
-Пока отдельных примеров использования не подготовлено, можно посмотреть тексты [тестов](tests/test_str.cpp),
-[бенчмарков](bench/bench_str.cpp), и [утилиты подготовки html](bench/process_result.cpp) из результатов бенчмарков.
-Также simstr используется в моём проекте [v8sqlite](https://github.com/orefkov/v8sqlite)
+## Usage examples
+While no separate usage examples have been prepared, you can look at the texts of [tests](tests/test_str.cpp),
+[benchmarks](bench/bench_str.cpp), and [html preparation utilities](bench/process_result.cpp) from the benchmark results.
+Also, simstr is used in my [v8sqlite](https://github.com/orefkov/v8sqlite) project
-## Сгенерированная документация
-[Находится здесь](https://snegopat.ru/simstr/docs/)
+## Generated documentation
+[Located here](https://snegopat.ru/simstr/docs/)
diff --git a/readme_ru.md b/readme_ru.md
new file mode 100644
index 0000000..b4c92a7
--- /dev/null
+++ b/readme_ru.md
@@ -0,0 +1,96 @@
+# simstr - библиотека строковых объектов и функций
+[](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml)
+
+Версия 1.2.4.
+
+[On English|По-английски](readme.md)
+
+В этой библиотеке содержится реализация нескольких видов строковых объектов и различных алгоритмов для работы со строками.
+
+Цель библиотеки - сделать работу со строками в С++ такой же простой и лёгкой, как во множестве других языков, особенно
+скриптовых, но при этом сохранив оптимальность и производительность на уровне С и C++, и даже улучшив их.
+
+Не секрет, что работа со строками в С++ зачастую доставляет боль. Класс `std::string` часто неудобен либо неэффективен.
+Многих функций, обычно необходимых при работе со строками, просто нет, и их каждому приходится писать самому.
+
+Эта библиотека не делалась как универсальный комбайн, который "может всё", я реализовывал то, что мне приходилось
+использовать в работе, стараясь сделать это наиболее эффективным способом, и скромно надеюсь, что кое-что у меня получилось
+и пригодится другим людям, либо напрямую, либо как источник идей.
+
+Библиотека не претендует на роль "поменял хедер и всё заработало лучше". Многие методы я старался делать совместимыми
+с `std::string` и `std::string_view`, но особо с этим не заморачивался. Переписывание старого кода на работу с simstr
+потребует некоторых усилий, но уверяю, что они окупятся. А новый код писать с её применением легко и доставляет удовольствие :)
+
+Основное отличие simstr от std::string - для работы со строками используется не единый универсальный класс, а несколько
+видов объектов, каждый из которых хорош для своих целей, и при этом хорошо взаимодействующих друг с другом.
+Если вы активно использовали std::string_view и понимали, в чём его преимущество и недостатки по сравнению с std::string,
+то подход simstr вам также будет понятен.
+
+## Основные возможности библиотеки
+- Строки `char`, `char16_t`, `char32_t`, `wchar_t`.
+- Прозрачное преобразование строк из одного типа символов в другой, с автоматической конвертацией между UTF-8, UTF-16, UTF-32,
+ используя [simdutf](https://github.com/simdutf/simdutf).
+- Расширяемая система "Строковых выражений". Позволяет эффективно реализовать преобразование и сложение (конкатенацию) строк, литералов,
+ чисел и возможно других объектов.
+- Строковые функции:
+ - Получение подстрок.
+ - Поиск подстрок и символов - с начала или с конца строки.
+ - Различный тримминг строк - справа, слева, везде, по пробельным символам, по заданным символам.
+ - Замена подстрок.
+ - Замена набора символов на набор соответствующих подстрок.
+ - Слияние (join) контейнеров строк в единую строку, с заданием разделителей и опций - "пропускать пустые", "разделитель после последней".
+ - Разбиение (split) строк на части по заданному разделителю. Разбиение возможно сразу в контейнер со строками, либо вызовом функтора для
+ каждой подстроки, либо путем итерации с помощью итератора `Splitter`.
+- Интеграция с функциями форматирования `format` и `sprintf` (с автоматическим увеличением буфера).
+ Форматирование возможно для строк `char`, `wchar_t` и строк, совместимых с `wchar_t` по размеру.
+ То есть под Windows это `char16_t`, под Linux - `char32_t`. Писать свою библиотеку форматирования не входило в мои замыслы.
+- Парсинг целых чисел с возможностью "тонкой" настройки при компиляции - можно задавать опции проверки переполнения,
+ пропуск пробельных символов, конкретное основание счисления либо автовыбор по префиксам `0x`, `0`, `0b`, `0o`,
+ допустимость знака `+`. Парсинг реализован для всех видов строк и символов.
+- Парсинг double пока реализован вызовом стандартной библиотеки и работает только для строк `char`, `wchar_t` и совместимых с
+ `wchar_t` по размеру типов.
+- Содержится минимальная поддержка Unicode при преобразовании `upper`, `lower` и регистро-независимом сравнении строк.
+ Работает только для символов первой плоскости Unicode (до 0xFFFF), а при смене регистра не учитываются случаи, когда один code point
+ может преобразовываться в несколько, то есть преобразование регистра символов соответствует `std::towupper`, `std::towlower` для unicode локали, только быстрее и может работать с любым видом символов.
+- Реализован `hash map` для ключей строкового типа, на базе `std::unordered_map`, с возможностью более эффективного хранения и
+ сравнения ключей по сравнению с ключами `std::string`. Поддерживается возможность регистро-независимого сравнения ключей (Ascii или
+ минимальный Unicode (см. предыдущий пункт)).
+
+## Основные объекты библиотеки
+- simple_str<K> - самая простая строка (или кусок строки), иммутабельная, не владеющая, аналог `std::string_view`.
+- simple_str_nt<K> - то же самое, только заявляет, что заканчивается 0. Для работы со сторонними C-API.
+- sstring<K> - shared string, иммутабельная, владеющая, с разделяемым буфером символов, поддержка SSO.
+- lstring<K, N> - local string, мутабельная, владеющая, с задаваемым размером SSO буфера.
+
+## Статьи
+- [Обзор и введение](docs/overview_ru.md)
+- [Обзорная статья на Хабре](https://habr.com/ru/articles/935590)
+- [Описание применяемой техники "Expression Templates"](https://habr.com/ru/articles/936468/)
+
+## Использование
+`simstr` состоит из трёх заголовочных файлов и двух исходников. Можно подключать как CMake проект через `add_subdirectory` (библиотека `simstr`),
+можно просто включить файлы в свой проект. Для сборки также требуется [simdutf](https://github.com/simdutf/simdutf) (при использовании CMake
+скачивается автоматически).
+
+Для работы `simstr` требуется компилятор стандарта не ниже С++20 - используются концепты и std::format.
+Работа проверялась под Windows на MSVC-19 и Clang-19, под Linux - на GCC-13 и Clang-21.
+Также проверялась работа в WASM, сборка в Emscripten 4.0.6, Clang-21.
+
+
+## Бенчмарки
+Бенчмарки производятся с использованием фреймворка [Google benchmark](https://github.com/google/benchmark).
+Постарался сделать замеры для наиболее типичных операций, встречающихся в обычной работе. Я проводил замеры на своём оборудовании, под
+Windows и Linux (в WSL), с использованием компиляторов MSVC, Clang, GCC. Сторонние результаты приветствуются.
+Также проводил замеры в WASM, сборка в Emscripten. Обращаю внимание, что под WASM в Emscripten собирается 32-битная сборка, а значит,
+размеры буферов SSO в объектах меньше.
+
+- [Исходный код бенчмарков](bench/bench_str.cpp)
+- [Результаты бенчмарков](https://snegopat.ru/simstr/results.html)
+
+## Примеры использования
+Пока отдельных примеров использования не подготовлено, можно посмотреть тексты [тестов](tests/test_str.cpp),
+[бенчмарков](bench/bench_str.cpp), и [утилиты подготовки html](bench/process_result.cpp) из результатов бенчмарков.
+Также simstr используется в моём проекте [v8sqlite](https://github.com/orefkov/v8sqlite)
+
+## Сгенерированная документация
+[Находится здесь](https://snegopat.ru/simstr/docs/)