Я бы не называл это генерацией кода. Это скорее сочетание двух
вещей: 1. Просчёт на этапе компиляции каких-то константных данных (в виде реально констант и в виде сформированных уникальных типов), описывающих свойства объекта c которым мы работаем.
2. На основе этих констант шаблоны инстанцируются в конкретные методы работы с объектом. И эти методы должны быть написаны.
Вот, например, метод записи в порт
template<uint32_t PM=PinsMask>
static inline void write(uint16_t data)
{
if constexpr (PM==0) return;
if constexpr (PM == 0xFFFF)
base()->ODR = data;
else if constexpr (PM == 0x00FF)
*pVU8(&base()->ODR) = data;
else if constexpr (PM == 0xFF00)
*(pVU8(&base()->ODR) + 1) = data >> 8;
else
{
base()->BSRR = (PM << 16) | (data & PM);
}
}
1. На этапе компиляции вычисляется маска.
2. На основе этой маски выберется оптимальный метод записи в порт, который и будет инстанцирован в прошивке из шаблона.
Создаётся впечатление магии или кодогенерации, но на самом деле это просто инстанцирование шаблона с оптимальным алгоритмом для конкретного случая. А вот вычисление маски может быть как простым в случае одного пина, так и зубодробительным, если у нас список пинов состоит из: пинов c разных портов, разных типов портов, других списков пинов. Тут и приходится уходить в метапрограммирование. И чем больше мы знаем об объекте на этапе компиляции, тем более изощрённые методы оптимизации можем применить. Ни один макрос такого никогда не сделает. На С такое только руками, но руками будет очень трудоёмко, поэтому С-программисты так не заморачиваются.