Хабрахабр

Работа со строками на этапе компиляции в современном C++

Если вы программируете на C++, то наверняка задавались вопросом почему нельзя сравнить два строковых литерала или выполнить их конкатенацию:

auto str = "hello" + "world"; // ошибка компиляции if ("hello" < "world") { // компилируется, но работает не так, как ожидалось // ...
}

Ломать стереотипы будем под катом, причем прямо на этапе компиляции. Впрочем, как говорится, "нельзя, но если очень хочется, то можно".

Зачем все это нужно

В проекте было несколько модулей, в которых были определены глобальные строковые константы: В одном из проектов, над которым я работал, было принято использовать std::string в качестве строковых констант.

// plugin.h const std::string PLUGIN_PATH = "/usr/local/lib/project/plugins/"; // ...

// sample_plugin.h const std::string SAMPLE_PLUGIN_LIB = PLUGIN_PATH + "sample.so"; // ...

SAMPLE_PLUGIN_PATH приняла значение "sample.so", несмотря на то, что PLUGIN_PATH имела значение "/usr/local/lib/project/plugins/", как и ожидалось. Думаю, вы уже догадались, что случилось в один прекрасный день. Все очень просто, порядок инициализации глобальных объектов не определен, в момент инициализации SAMPLE_PLUGIN_PATH переменная PLUGIN_PATH была пуста. Как это могло произойти?

Во-первых, брошенное при создании глобального объекта исключение не отлавливается. Кроме того, такой подход имеет еще ряд недостатков. Во-вторых, инициализация происходит при выполнении программы, что тратит драгоценное процессорное время.

Именно тогда у меня возникла идея о работе со строками на этапе компиляции, которая в итоге и привела к написанию этой статьи.

Назовем такие строки статическими. В этой статье рассмотрим строки, операции над которыми можно проводить на этапе компиляции.

Исходные коды библиотеки доступны на github, ссылка в конце статьи. Все реализованные операции были включены в библиотеку для работы со статическими строками.

Для использования библиотеки требуется как минимум C++14.

Определение статической строки

Определим статическую строку как массив символов, для удобства будем считать, что строка всегда оканчивается нулевым символом:

template<size_t Size>
using static_string = std::array<const char, Size>; constexpr static_string<6> hello = ;

Мне этот вариант показался более трудоемким и менее удобным. Здесь можно пойти по другому пути, и определить строку как кортеж символов. Поэтому здесь он рассмотрен не будет.

Создание статической строки

Во-первых, нам нужно заранее вычислять длину массива. Посмотрите на определение строки hello выше, оно просто ужасно. В-третьих, все эти запятые, скобки и кавычки. Во-вторых, нужно не забыть записать нулевой символ в конец. Хотелось бы написать как-нибудь так: Определенно, с этим нужно что-то делать.

constexpr auto hello = make_static_string("hello");

Здесь нам поможет одна из форм вариативного шаблона, которая позволяет развернуть шаблонные аргументы как индексы для агрегатной инициализации нашей статической строки из строкового литерала:

template<size_t Size, size_t ... Indexes>
constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const char (& str)[Size]) { return {str[Indexes] ..., '\0'};
} constexpr auto hello = make_static_string<0, 1, 2, 3, 4>("hello"); // hello == "hello"

Здесь также отметим, что если указывать не все индексы, то можно получить подстроку строкового литерала, а если записать их в обратном порядке, то и его инверсию: Уже лучше, но индексы все же приходится писать руками.

constexpr hello1 = make_static_string<1, 2, 3>("hello"); // hello1 == "ell"
constexpr hello2 = make_static_string<4, 3, 2, 1, 0>("hello"); // hello2 == "olleh"

Это соображение очень пригодится нам в дальнейшем.

Для этого применим трюк с наследованием. Теперь нам нужно как-то сгенерировать последовательность индексов строки. Определим пустую структуру (нужно же что-то наследовать) с набором искомых индексов в качестве шаблонных параметров:

template<size_t ... Indexes>
struct index_sequence {};

Определим структуру-генератор, которая будет генерировать индексы по одному, храня счетчик в первом параметре:

template<size_t Size, size_t ... Indexes>
struct make_index_sequence : make_index_sequence<Size - 1, Size - 1, Indexes ...> {};

Позаботимся и о конечной точке рекурсии, когда все индексы сгенерированы (счетчик равен нулю), мы отбрасываем счетчик и генератор превращается в нужную нам последовательность:

template<size_t ... Indexes>
struct make_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};

В итоге, функция создания статической строки будет выглядеть так:

template<size_t Size, size_t ... Indexes>
constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const char (& str)[Size], index_sequence<Indexes ...>) { return {str[Indexes] ..., '\0'};
}

Напишем аналогичную функцию для статической строки, она пригодится нам далее:

template<size_t Size, size_t ... Indexes>
constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const static_string<Size>& str, index_sequence<Indexes ...>) { return {str[Indexes] ..., '\0'};
}

Но я, для краткости, упоминать об этом не буду. В дальнейшем, для каждой функции, принимающей строковый литерал foo(const char (& str)[Size]) будем писать аналогичную функцию, принимающую статическую строку foo(const static_string<Size>& str).

Поскольку длина строкового литерала нам известна, мы можем автоматически сгенерировать последовательность индексов, напишем обертку для функции выше:

template<size_t Size>
constexpr static_string<Size> make_static_string(const char (& str)[Size]) { return make_static_string(str, make_index_sequence<Size - 1>{});
}

Эта функция позволяет сделать ровно то, что мы хотели в начале главы.

В случае отсутствия аргументов будем возвращать пустую статическую строку, которая состоит только из нулевого символа:

constexpr static_string<1> make_static_string() { return {'\0'};
}

Также нам понадобится создавать строку из кортежа символов:

template<char ... Chars>
constexpr static_string<sizeof ... (Chars) + 1> make_static_string(char_sequence<Chars ...>) { return {Chars ..., '\0'};
}

Поэтому, если что-то осталось непонятным, лучше перечитать главу еще раз. К слову, все, что далее будет описано в этой статье, опирается на приемы, которые описаны в данной главе.

Вывод статической строки в поток

Так как наша строка оканчивается нулевым символом, достаточно вывести в поток данные массива: Здесь все просто.

template<size_t Size>
std::ostream& operator<<(std::ostream& os, const static_string<Size>& str) { os << str.data(); return os;
}

Преобразование статической строки в std::string

Инициализируем строку данными массива: Здесь тоже ничего сложного.

template<size_t Size>
std::string to_string(const static_string<Size>& str) { return std::string(str.data());
}

Сравнение статических строк

Поскольку constexpr for еще не изобрели, воспользуемся рекурсией и тернарным оператором: Будем сравнивать строки посимвольно, пока не выявим различия, либо не достигнем конца хотя бы одной из строк.

template<size_t Size1, size_t Size2>
constexpr int static_string_compare( const static_string<Size1>& str1, const static_string<Size2>& str2, int index = 0) { return index >= Size1 && index >= Size2 ? 0 : index >= Size1 ? -1 : index >= Size2 ? 1 : str1[index] > str2[index] ? 1 : str1[index] < str2[index] ? -1 : static_string_compare(str1, str2, index + 1);
}

В дальнейшем, нам понадобится расширенная версия компаратора, введем индивидуальный индекс для каждой их строк, также ограничим количество сравниваемых символов:

template<size_t Size1, size_t Size2>
constexpr int static_string_compare( const static_string<Size1>& str1, size_t index1, const static_string<Size2>& str2, size_t index2, size_t cur_length, size_t max_length) { return cur_length > max_length || (index1 >= Size1 && index2 >= Size2) ? 0 : index1 >= Size1 ? -1 : index2 >= Size2 ? 1 : str1[index1] > str2[index2] ? 1 : str1[index1] < str2[index2] ? -1 : static_string_compare(str1, index1 + 1, str2, index2 + 1, cur_length + 1, max_length);
}

Такая версия компаратора позволит нам сравнивать не только строки целиком, но и отдельные подстроки.

Конкатенация статических строк

Инициализируем массив сначала символами первой строки (без учета нулевого символа), затем второй, и наконец добавляем нулевой символ в конец: Для конкатенации используем тот же вариативный шаблон, что и в главе про создание статической строки.

template<size_t Size1, size_t ... Indexes1, size_t Size2, size_t ... Indexes2>
constexpr static_string<Size1 + Size2 - 1> static_string_concat_2( const static_string<Size1>& str1, index_sequence<Indexes1 ...>, const static_string<Size2>& str2, index_sequence<Indexes2 ...>) { return {str1[Indexes1] ..., str2[Indexes2] ..., '\0'};
} template<size_t Size1, size_t Size2>
constexpr static_string<Size1 + Size2 - 1> static_string_concat_2( const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_concat_2(str1, make_index_sequence<Size1 - 1>{}, str2, make_index_sequence<Size2 - 1>{});
}

Реализуем также вариативный шаблон для конкатенации произвольного количества строк или строковых литералов:

constexpr auto static_string_concat() { return make_static_string();
} template<typename Arg, typename ... Args>
constexpr auto static_string_concat(Arg&& arg, Args&& ... args) { return static_string_concat_2(make_static_string(std::forward<Arg>(arg)), static_string_concat(std::forward<Args>(args) ...));
}

Операции поиска в статической строке

Рассмотрим операции поиска символа и подстроки в статической строке.

Поиск символа в статической строке

Также дадим возможность задавать начальную позицию поиска и порядковый номер совпадения: Поиск символа не представляет особенной сложности, рекурсивно проверяем символы по всем индексам и возвращаем первый индекс в случае совпадения.

template<size_t Size>
constexpr size_t static_string_find(const static_string<Size>& str, char ch, size_t from, size_t nth) { return Size < 2 || from >= Size - 1 ? static_string_npos : str[from] != ch ? static_string_find(str, ch, from + 1, nth) : nth > 0 ? static_string_find(str, ch, from + 1, nth - 1) : from;
}

Определим ее следующим образом: Константа static_string_npos указывает на то, что поиск не увенчался успехом.

constexpr size_t static_string_npos = std::numeric_limits<size_t>::max();

Аналогично реализуем поиск в обратном направлении:

template<size_t Size>
constexpr size_t static_string_rfind(const static_string<Size>& str, char ch, size_t from, size_t nth) { return Size < 2 || from > Size - 2 ? static_string_npos : str[from] != ch ? static_string_rfind(str, ch, from - 1, nth) : nth > 0 ? static_string_rfind(str, ch, from - 1, nth - 1) : from;
}

Определение вхождения символа в статическую строку

Для определения вхождения символа достаточно попробовать поискать его:

template<size_t Size>
constexpr bool static_string_contains(const static_string<Size>& str, char ch) { return static_string_find(str, ch) != static_string_npos;
}

Подсчет количества вхождений символа в статическую строку

Подсчет количества вхождений реализуется тривиально:

template<size_t Size>
constexpr size_t static_string_count(const static_string<Size>& str, char ch, size_t index) { return index >= Size - 1 ? 0 : (str[index] == ch ? 1 : 0) + static_string_count(str, ch, index + 1);
}

Поиск подстроки в статической строке

Так как предполагается, что статические строки будут относительно небольшими, не будем здесь реализовывать алгоритм Кнута-Морриса-Пратта, реализуем простейший квадратичный алгоритм:

template<size_t Size, size_t SubSize>
constexpr size_t static_string_find(const static_string<Size>& str, const static_string<SubSize>& substr, size_t from, size_t nth) { return Size < SubSize || from > Size - SubSize ? static_string_npos : static_string_compare(str, from, substr, 0, 1, SubSize - 1) != 0 ? static_string_find(str, substr, from + 1, nth) : nth > 0 ? static_string_find(str, substr, from + 1, nth - 1) : from;
}

Аналогично реализуем поиск в обратном направлении:

template<size_t Size, size_t SubSize>
constexpr size_t static_string_rfind(const static_string<Size>& str, const static_string<SubSize>& substr, size_t from, size_t nth) { return Size < SubSize || from > Size - SubSize ? static_string_npos : static_string_compare(str, from, substr, 0, 1, SubSize - 1) != 0 ? static_string_rfind(str, substr, from - 1, nth) : nth > 0 ? static_string_rfind(str, substr, from - 1, nth - 1) : from;
}

Определение вхождения подстроки в статическую строку

Для определения вхождения подстроки достаточно попробовать поискать ее:

template<size_t Size, size_t SubSize>
constexpr bool static_string_contains(const static_string<Size>& str, const static_string<SubSize>& substr) { return static_string_find(str, substr) != static_string_npos;
}

Определение, начинается/кончается ли статическая строка с/на заданной подстроки

Применив ранее описанный компаратор мы можем определить, начинается ли статическая строка с заданной подстроки:

template<size_t SubSize, size_t Size>
constexpr bool static_string_starts_with(const static_string<Size>& str, const static_string<SubSize>& prefix) { return SubSize > Size ? false : static_string_compare(str, 0, prefix, 0, 1, SubSize - 1) == 0;
}

Аналогично для окончания статической строки:

template<size_t SubSize, size_t Size>
constexpr bool static_string_ends_with(const static_string<Size>& str, const static_string<SubSize>& suffix) { return SubSize > Size ? false : static_string_compare(str, Size - SubSize, suffix, 0, 1, SubSize - 1) == 0;
}

Работа с подстроками статической строки

Здесь рассмотрим операции, связанные с подстроками статической строки.

Получение подстроки, префикса и суффикса статической строки

Как мы отметили ранее, для получения подстроки нужно сгенерировать последовательность индексов, с заданным начальным и конечным индексами:

template<size_t Begin, size_t End, size_t ... Indexes>
struct make_index_subsequence : make_index_subsequence<Begin, End - 1, End - 1, Indexes ...> {}; template<size_t Pos, size_t ... Indexes>
struct make_index_subsequence<Pos, Pos, Indexes ...> : index_sequence<Indexes ...> {};

Реализуем получение подстроки с проверкой начала и конца подстроки с помощью static_assert:

template<size_t Begin, size_t End, size_t Size>
constexpr auto static_string_substring(const static_string<Size>& str) { static_assert(Begin <= End, "Begin is greater than End (Begin > End)"); static_assert(End <= Size - 1, "End is greater than string length (End > Size - 1)"); return make_static_string(str, make_index_subsequence<Begin, End>{});
}

Префикс — это подстрока, начало которой совпадает с началом исходной статической строки:

template<size_t End, size_t Size>
constexpr auto static_string_prefix(const static_string<Size>& str) { return static_string_substring<0, End>(str);
}

Аналогично для суффикса, только совпадает конец:

template<size_t Begin, size_t Size>
constexpr auto static_string_suffix(const static_string<Size>& str) { return static_string_substring<Begin, Size - 1>(str);
}

Разделение статической строки на две части по заданному индексу

Чтобы разделить статическую строку по заданному индексу, достаточно вернуть префикс и суффикс:

template<size_t Index, size_t Size>
constexpr auto static_string_split(const static_string<Size>& str) { return std::make_pair(static_string_prefix<Index>(str), static_string_suffix<Index + 1>(str));
}

Реверсирование статической строки

Для реверсирования статической строки напишем генератор индексов, который генерирует индексы в обратном порядке:

template<size_t Size, size_t ... Indexes>
struct make_reverse_index_sequence : make_reverse_index_sequence<Size - 1, Indexes ..., Size - 1> {}; template<size_t ... Indexes>
struct make_reverse_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {};

Теперь реализуем функцию, которая реверсирует статическую строку:

template<size_t Size>
constexpr auto static_string_reverse(const static_string<Size>& str) { return make_static_string(str, make_reverse_index_sequence<Size - 1>{});
}

Вычисление хэша статической строки

Вычислять хэш будем по следующей формуле:

H(s) = (s0 + 1) ⋅ 330 + (s1 + 1) ⋅ 331 +… + (sn — 1 + 1) ⋅ 33n — 1 + 5381 ⋅ 33n mod 264

template<size_t Size>
constexpr unsigned long long static_string_hash(const static_string<Size>& str, size_t index) { return index >= Size - 1 ? 5381ULL : static_string_hash(str, index + 1) * 33ULL + str[index] + 1;
}

Преобразование числа в статическую строку и обратно

Для простоты будем считать, что числа представлены типами long long и unsigned long long, это типы большой разрядности, то есть подходят для большинства случаев. В этой главе рассмотрим преобразование статической строки в целое число, а также обратное преобразование.

Преобразование числа в статическую строку

Для преобразования числа в статическую строку нам нужно получить все цифры числа, преобразовать их в соответствующие символы и составить из этих символов строку.

Определим последовательность символов: Для получения всех цифр числа будем использовать генератор, аналогичный генератору последовательности индексов.

template<char ... Chars>
struct char_sequence {};

Реализуем генератор символов цифр, храня текущее число в первом параметре, а цифры в следующих, очередная цифра добавляется в начало последовательности, а число делится на десять:

template<unsigned long long Value, char ... Chars>
struct make_unsigned_int_char_sequence : make_unsigned_int_char_sequence<Value / 10, '0' + Value % 10, Chars ...> {};

Если текущее число равно 0, то отбрасываем его, возвращая последовательность цифр, больше преобразовывать нечего:

template<char ... Chars>
struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {};

Следует также учесть случай, когда первоначальное число равно нулю, в этом случае нужно вернуть нулевой символ, иначе нуль будет преобразован в пустую последовательность символов, а потом и в пустую строку:

template<>
struct make_unsigned_int_char_sequence<0> : char_sequence<'0'> {};

Определим новый генератор, добавив в начало еще один шаблонный параметр — знак преобразуемого числа: Реализованный генератор прекрасно работает для положительных чисел, но не пригоден для отрицательных.

template<bool Negative, long long Value, char ... Chars>
struct make_signed_int_char_sequence {};

Будем обрабатывать число также, как показано выше, но с учетом знака:

template<long long Value, char ... Chars>
struct make_signed_int_char_sequence<true, Value, Chars ...> : make_signed_int_char_sequence<true, Value / 10, '0' + -(Value % 10), Chars ...> {}; template<long long Value, char ... Chars>
struct make_signed_int_char_sequence<false, Value, Chars ...> : make_signed_int_char_sequence<false, Value / 10, '0' + Value % 10, Chars ...> {};

Здесь нельзя -Value % 10, так как диапазон отрицательных чисел на одно число шире диапазона положительных и модуль минимального числа выпадает из множества допустимых значений. Здесь есть один тонкий момент, обратите внимание на -(Value % 10).

Отбрасываем число после обработки, если оно отрицательно, добавим символ знака минуса:

template<char ... Chars>
struct make_signed_int_char_sequence<true, 0, Chars ...> : char_sequence<'-', Chars ...> {}; template<char ... Chars>
struct make_signed_int_char_sequence<false, 0, Chars ...> : char_sequence<Chars ...> {};

Отдельно позаботимся о преобразовании нуля:

template<>
struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {};

Наконец, реализуем функции преобразования:

template<unsigned long long Value>
constexpr auto uint_to_static_string() { return make_static_string(make_unsigned_int_char_sequence<Value>{});
} template<long long Value>
constexpr auto int_to_static_string() { return make_static_string(make_signed_int_char_sequence<(Value < 0), Value>{});
}

Преобразование статической строки в число

Выполняем все действия рекурсивно, для пустой строки возвращаем нуль: Для преобразования статической строки в число нужно преобразовать символы в цифры, а затем сложить их, предварительно домножив на соответствующую степень десятки.

template<size_t Size>
constexpr unsigned long long static_string_to_uint(const static_string<Size>& str, size_t index) { return Size < 2 || index >= Size - 1 ? 0 : (str[index] - '0') + 10ULL * static_string_to_uint(str, index - 1);
} template<size_t Size>
constexpr unsigned long long static_string_to_uint(const static_string<Size>& str) { return static_string_to_uint(str, Size - 2);
}

Для преобразования знаковых чисел, нужно учесть, что отрицательные числа начинаются с символа знака минуса:

template<size_t Size>
constexpr long long static_string_to_int(const static_string<Size>& str, size_t index, size_t first) { return index < first || index >= Size - 1 ? 0 : first == 0 ? (str[index] - '0') + 10LL * static_string_to_int(str, index - 1, first) : -(str[index] - '0') + 10LL * static_string_to_int(str, index - 1, first);
} template<size_t Size>
constexpr long long static_string_to_int(const static_string<Size>& str) { return Size < 2 ? 0 : str[0] == '-' ? static_string_to_int(str, Size - 2, 1) : static_string_to_int(str, Size - 2, 0); }

Вопросы удобства использования библиотеки

В этой главе рассмотрим как можно сделать использование библиотеки более удобным. К этому моменту библиотеку уже возможно полноценно использовать, но некоторые моменты вызывают неудобство.

Объект статической строки

Это позволит использовать более короткие имена методов, а также реализовать операторы сравнения: Упакуем строку и реализованные методы в объект.

template<size_t Size> struct static_string { constexpr size_t length() const { return Size - 1; } constexpr size_t size() const { return Size; } constexpr size_t begin() const { return 0; } constexpr size_t end() const { return Size - 1; } constexpr size_t rbegin() const { return Size - 2; } constexpr size_t rend() const { return std::numeric_limits<size_t>::max(); } constexpr bool empty() const { return Size < 2; } constexpr auto reverse() const { return static_string_reverse(*this); } template<size_t Begin, size_t End> constexpr auto substring() const { return static_string_substring<Begin, End>(*this); } template<size_t End> constexpr auto prefix() const { return static_string_prefix<End>(*this); } template<size_t Begin> constexpr auto suffix() const { return static_string_suffix<Begin>(*this); } constexpr size_t find(char ch, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t find(const static_string<SubSize>& substr, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t find(const char (& substr)[SubSize], size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } constexpr size_t rfind(char ch, size_t from = Size - 2, size_t nth = 0) const { return static_string_rfind(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t rfind(const static_string<SubSize>& substr, size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t rfind(const char (& substr)[SubSize], size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } constexpr bool contains(char ch) const { return static_string_contains(*this, ch); } template<size_t SubSize> constexpr bool contains(const static_string<SubSize>& substr) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool contains(const char (& substr)[SubSize]) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool starts_with(const static_string<SubSize>& prefix) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool starts_with(const char (& prefix)[SubSize]) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool ends_with(const static_string<SubSize>& suffix) const { return static_string_ends_with(*this, suffix); } template<size_t SubSize> constexpr bool ends_with(const char (& suffix)[SubSize]) const { return static_string_ends_with(*this, suffix); } constexpr size_t count(char ch) const { return static_string_count(*this, ch); } template<size_t Index> constexpr auto split() const { return static_string_split<Index>(*this); } constexpr unsigned long long hash() const { return static_string_hash(*this); } constexpr char operator[](size_t index) const { return data[index]; } std::string str() const { return to_string(*this); } std::array<const char, Size> data;
};

Операторы сравнения

Определим глобальные операторы сравнения: Использование компаратора в виде функции неудобно и нечитаемо.

template<size_t Size1, size_t Size2>
constexpr bool operator<(const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_compare(str1, str2) < 0;
}

Здесь приводить их нет смысла из-за тривиальности. Аналогично реализуем остальные операторы > <= >= == !=, для всех вариаций аргументов статических строк и строковых литералов.

Макросы работы с числами

Для удобства преобразования числа в статическую строку и обратно определим соотвествующие макросы:

#define ITOSS(x) int_to_static_string<(x)>()
#define UTOSS(x) uint_to_static_string<(x)>()
#define SSTOI(x) static_string_to_int((x))
#define SSTOU(x) static_string_to_uint((x))

Примеры использования библиотеки

Ниже приведены примеры реального использования реализованной библиотеки.

Конкатенация статических строк и строковых литералов:

constexpr auto hello = make_static_string("Hello");
constexpr auto world = make_static_string("World");
constexpr auto greeting = hello + ", " + world + "!"; // greeting == "Hello, World!"

Конкатенация статических строк, строковых литералов и чисел:

constexpr int apples = 5;
constexpr int oranges = 7;
constexpr auto message = static_string_concat("I have ", ITOSS(apples), " apples and ", ITOSS(oranges), ", so I have ", ITOSS(apples + oranges), " fruits");
// message = "I have 5 apples and 7 oranges, so I have 12 fruits"

constexpr unsigned long long width = 123456789ULL;
constexpr unsigned long long height = 987654321ULL;
constexpr auto message = static_string_concat("A rectangle with width ", UTOSS(width), " and height ", UTOSS(height), " has area ", UTOSS(width * height));
// message = "A rectangle with width 123456789 and height 987654321 has area 121932631112635269"

constexpr long long revenue = 1'000'000LL;
constexpr long long costs = 1'200'000LL;
constexpr long long profit = revenue - costs;
constexpr auto message = static_string_concat("The first quarter has ended with net ", (profit >= 0 ? "profit" : "loss "), " of $", ITOSS(profit < 0 ? -profit : profit));
// message == "The first quarter has ended with net loss of $200000"

Парсинг URL:

constexpr auto url = make_static_string("http://www.server.com:8080");
constexpr auto p = url.find("://");
constexpr auto protocol = url.prefix<p>(); // protocol == "http"
constexpr auto sockaddr = url.suffix<p + 3>();
constexpr auto hp = sockaddr.split<sockaddr.find(':')>();
constexpr auto host = hp.first; // host == "www.server.com"
constexpr int port = SSTOI(hp.second); // port == 8080

Итерация по символам в обоих направлениях:

constexpr auto str = make_static_string("Hello");
for (size_t i = str.begin(); i != str.end(); ++i) // вперед std::cout << str[i];
std::cout << std::endl; // Hello
for (size_t i = str.rbegin(); i != str.rend(); --i) // назад std::cout << str[i];
std::cout << std::endl; // olleH

Ссылки

Библиотеку, реализующую все вышеперечисленное, можно взять в моем github

Спасибо за внимание, замечания и дополнения приветствуются.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть