Мужики, прошу прощения, в самом начале я написал
А потом, после того как показал реализацию на constexpr
функции как-то забыл раскрыть эту фразу. Извиняюсь, сейчас исправлю.
Итак, кроме constexpr
функций для вычислений во время компиляции также годятся шаблоны и препроцессор. И там и там можно сделать то же самое, что мы делали выше, т.е. посчитать длину utf-8 строки во время компиляции.
Ну, с препроцессором сделать-то можно, но гороху откушать надо немало, а я только что пообедал – не хочется (но сделать точно можно!). (справедливости ради, на препроцессоре можно и просто сделать, если воспользоваться замечательной библиотекой Boost C++, но я далёк от мысли здесь её рекламировать).
А вот с шаблонами могу показать, смотрите. Я постарался прокомментировать, так-то там всего шесть строк, если комментариев не считать. Сделано также, как и раньше, если WITH_CALC true
то идёт расчёт, а если false
, вставляются голимые константы. Как видите размер кода и данных ни на байт не отличается.
#define WITH_CALC true
//
// Строковую константу для передачи в качестве параметра шаблона,
// надо объявлять ИМЕННО МАССИВОМ, А НЕ УКАЗАТЕЛЕМ.
// Запись
// constexpr char * str = "Ну, сказала Сова ..."; // 20 символов
// НЕ ПРОКАТИТ
//
constexpr char str [] = "Ну, сказала Сова ..."; // 20 символов
//
#ifdef WITH_CALC
//
// Общая идея шаблонных вычислений: строится базовый шаблон, который вычисляет
// необходимую величину "в большинстве случаев", и к нему добавляются специализированные шаблоны
// с тем же именем, которые вычисляют частные случаи (при каких-то конкретных значения параметров)
//
// В данном примере базовый шаблон просто считает, что длина строки равна 1 + длина строки, оставшейся
// после отбрасывания первого (текущего) символа. К нему добавляются два специализированных шаблона.
// Первый - на случай, когда текущий символ является специальным префиксом UTF8, тогда он не
// учитывается, а длина строки равна длине оставшейся после отбрасывания текущего символа строки.
// Второй - на случай, когда текущий символ равен '\0' - это позволяет прервать рекурсию и завершить
// вычисления.
//
// Базовый шаблон
//
// Параметры (для обращения нужен только первый, остальные - внутренняя кухня):
// p - строка для подсчёта длина. Должна быть constexpr и массив, а не указатель (см. выше)
// ds - номер текущего символа в массиве. Изначально - 0
// с - текущий символ массива
// isUTFPrefix - true, если текущий символ является префиксом символа UTF8 и false в противном случае
// Результат:
// создаёт структуру LC с единственным статическим полем length
// Алгоритм:
// Присваивает полю length 1 + длину оставшейся строки (начиная с p[ds+1])
//
template<const char * p, size_t ds = 0, char c = p[ds], bool isUTFPrefix = (p[ds] & 0xc0) == 0x80>
struct LC { static const size_t length = 1 + LC<p,ds+1,p[ds+1]>::length; };
//
// Первый специализированный шаблон (для случая, когда isUTFPrefix равен true)
// Отличается от базового только тем, что не прибавляет 1 в результат
//
template<const char * p, size_t ds, char c>
struct LC<p, ds, c, true> { static const size_t length = LC<p,ds+1,p[ds+1]>::length; };
//
// Второй специализированный шаблон (для случая, когда текущий символ равен '\0')
// Просто говорит, что длина строки 0 и больше не вызывает шаблонов (заканчивает рекурсию)
//
template<const char * p, size_t ds>
struct LC<p, ds, '\0', false> { static const size_t length = 0; };
//
// Здесь мы просто вызываем наш шаблон и константа theLength получает значение,
// равное вычисленной длине строки
//
constexpr size_t theLength = LC<str>::length;
#else // WITH_CALC
// А тут мы ничего не вычисляем, просто забиваем готовую константу
constexpr size_t theLength = 20;
//
#endif // WITH_CALC
//
void setup(void) {
Serial.begin(9600);
Serial.print("Длина (\"");
Serial.print(str);
Serial.print("\": ");
Serial.println(theLength);
}
void loop(void) {}