Ограничения синтаксиса имени переменной функции `##`

Иногда синтаксис C/C++ поражает меня, и сегодня один из таких дней. Я наткнулся на некоторый код, который использует макросы с ## генерировать уникальные и индивидуальные методы доступа к конкретным аппаратным регистрам (показано ниже).

Вопрос: Какие ограничения или проблемы существуют с ## синтаксис?

static uint8_t write(uint16_t _addr, uint8_t _data);  // Implemented elsewhere
static uint8_t read(uint16_t addr);                   // Implemented elsewhere

#define __GP_REGISTER8(name, address)             \
  static inline void write##name(uint8_t _data) { \
    write(address, _data);                        \
  }                                               \
  static inline uint8_t read##name() {            \
    return read(address);                         \
  }

__GP_REGISTER8 (A, 0x0000);    // Register A
__GP_REGISTER8 (B, 0x0001);    // Register B
__GP_REGISTER8 (C, 0x0002);    // Register C

#undef __GP_REGISTER8

После реализации предыдущих макросов у вас теперь есть возможность совершать следующие вызовы:

writeA(0xA5);
writeB(0x5A);
uint8_t c_value = readC();

Как ## работать точно (т.е. только в макросах и т. д.)? Кроме того, люди не используют это, потому что это действительно не предлагает никаких преимуществ, и это больше работы, или есть другие побочные эффекты / недостатки использования этой техники?

1 ответ

Решение

Это (почти) не о синтаксисе C++, а о языке препроцессора C. Эта функция называется вставкой или объединением токенов. Из руководства GCC:

## Оператор предварительной обработки выполняет вставку токена. Когда макрос раскрывается, два токена на каждой стороне каждого ## оператор объединяется в один токен, который затем заменяет ## и два оригинальных токена в расширении макроса.

Итак, в вашем (упрощенном) примере

__GP_REGISTER8(A, 0x0000)

расширяется (путем подстановки строк) до

static inline void write ## A(uint8_t _data) { write(0x0000, _data); }

Затем препроцессор заменяет write ## A тройной с writeA как вы заметили.

Какие ограничения или проблемы существуют с ## синтаксис?

Это грязная хитрость макросов, которая может сбить людей с толку, как и вас. Ограничения вполне очевидны: вы можете выполнять вставку токенов - ни меньше, ни больше. Позвольте мне еще раз подчеркнуть, что все это происходит до того, как компилятор C или C++ увидит код. Поэтому, если вы "вызовите" такой макрос с бессмысленными аргументами, препроцессор все равно будет радостно его развернуть, и только тогда компилятор увидит мусор и выведет сообщение об ошибке, в котором мало помощи. Тем не менее, если макрос используется внутри заголовочного файла для очистки повторяющихся объявлений, это, вероятно, хорошо.

Подобные трюки потеряли свою значимость благодаря растущей мощи метапрограммирования шаблонов C++. Можно с полным основанием утверждать, что функциональность, реализованная с помощью опубликованных вами макросов, могла бы быть реализована с использованием шаблонных функций, как в

enum class Registers { A = 0x0000, B = 0x0001, C = 0x0002; };

template<Registers R>
void
write(uint16_t data)
{
    write(static_cast<int>(R), data);
}

а затем используется как

write<Registers::A>(0xA5);
Другие вопросы по тегам