Расчет полей структуры на этапе компиляции

Мужики, прошу прощения, в самом начале я написал

А потом, после того как показал реализацию на 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) {}
5 лайков