philosoraptor, я читал и эту статью, и ей подобные. И тут есть несколько моментов.
1. Автор рассматривает исключительно задачу работы с периферией, причем исключительно с портами. Выше же шла речь о более широком применении С++, в частности, и в основной логике - иначе зачем вообще заморачиваться?
2. Автор использует исключительно статические классы и ни словом не заикнулся (логично) про создание их экземпляров. И все равно, несмотря на все изгибание C++ для применимости в эмбеде, не обошлось без ограничений:
- "От динамической конфигурации линий ввода-вывода сразу отказываемся из-за необходимости доступа к портам через указатель со всеми вытекающими последствиями."
- "Мы не можем прочитать состояние выходных линий регистра – он всегда работает на выход, поэтому функцию чтения состояния не реализуем и не объявляем. Попытка прочитать состояние такого пора вызовет ошибку компиляции."
А динамическая конфигурация бывает нужна. Например, чтобы имитировать открытый коллектор на AVR.
Неизвестно, на какие компромиссы придется пойти при разработке API к другим специфичным блокам, которым периодически бывают нужны, например, особые последовательности доступа.
3. И несмотря на все ограничения код получился тяжеловесным и сложным для понимания. Даже сам автор в некоторый момент устал: "Обобщая доступ к отдельным битам в списке линий приходим к концепции среза. Не буду вдаваться в подробности их реализации".
4. В синтетических примерах автора результат дизассемблирования выглядит неплохо, но где гарантия, что в каких-то реальных условиях что-то не пойдет не так, и компилятор не сгенерирует код для того, что по задумке должен вычислять во время компиляции?
Родственный пример (для AVR-libc) - функции задержки, например, _delay_ms(). Если подставить в нее не константу, а переменную, будет сюрприз. Здесь может произойти нечто подобное.
Кстати, рекурсия, используемая автором, запрещена стандартом MISRA.
Код: Выделить всё
struct MakePinList
{
private:
// рекурсивно проходим все параметры
// на следующей итерации Position увеличится на 1,
// а T2 превратится в T1 и так далее
typedef typename MakePinList
<
Position + 1,
...
5. В целом можно сказать, что
фактически автор лишь использует механизм шаблонов C++ как продвинутый препроцессор. Впрочем, и без классического препроцессора не обходится:
Код: Выделить всё
#define MAKE_PORT(portName, className, ID) \
class className{\
public:\
typedef uint8_t DataT;\
public:\
...
Это вообще в некоторой степени противоречит идеологии C++.
Разница между теорией и практикой на практике гораздо больше, чем в теории.