Как использовать атрибут формата GCC printf с вариабельными шаблонами C++11?

У меня есть класс C++, который является интерфейсом для системы журналирования. Его функция регистрации реализована с использованием шаблонов переменных C++11:

template <typename... Args>
void Frontend::log(const char *fmt, Args&&... args) {
  backend->true_log(fmt, std::forward<Args>(args)...);
}

Каждый бэкэнд регистрации реализует свою собственную версию true_log, который, помимо прочего, использует перенаправленные параметры для вызова vsnprintf, Например:

void Backend::true_log(const char *fmt, ...) {
  // other stuff..
  va_list ap;
  va_start(ap, fmt);
  vsnprintf(buffer, buffer_length, fmt, ap);
  va_end(ap);
  // other stuff..
}

Все отлично работает, и я счастлив.

Теперь я хочу добавить статическую проверку на log() параметры: в частности, я хотел бы использовать атрибут формата GCC printf.

Я начал с пометки log() функция с __attribute__ ((format (printf, 2, 3))) (как this это первый "скрытый" параметр, мне нужно сместить индексы параметров на единицу). Это не работает, потому что, если происходит сбой с ошибкой компиляции:

error: args to be formatted is not ‘...’

Затем я попытался добавить тот же атрибут к true_log() функция. Он компилируется, но проверка ошибок на самом деле не выполняется: я пытался перейти к log() некоторые недопустимые комбинации формат / переменная, и не было выдано предупреждение. Может быть, этот вид проверки "слишком поздний", или, другими словами, информация о переменной была потеряна в цепочке вызовов?

В крайнем случае, если я аннотировал log() с __attribute__ ((format (printf, 2, 0))), Я получаю предупреждения о неправильных строках формата, но не будет выдана диагностика для недопустимых комбинаций формат / переменная.

Резюмируя проблему: как я могу иметь полную проверку формата из GCC, если я использую вариационные шаблоны C++11?

2 ответа

Решение

Я не верю, что ты можешь. Могу поспорить, что GCC проверяет строку формата, только если она является литералом. Вот почему выкладывать format атрибут на true_log не работает - эта функция вызывается с тем, что выглядит (синтаксически) как строка, определенная во время выполнения. Положить его на log напрямую обойдется, но потребует format атрибуты для поддержки шаблона с переменными значениями, что вы доказали, что это не так.

Я предлагаю вам взглянуть на больше C++- иш способов сделать форматированный вывод. Есть, например, boost::format который работает так же, как printf, но динамически проверяет, что число и типы типов параметров соответствуют строке формата. Тем не менее, он не использует шаблоны с переменными значениями, а использует параметры, передаваемые ему (через оператор%), один за другим.

Для справки, я в итоге полностью удалил шаблоны C++11 и использовал традиционный va_list,

__attribute__((format(printf, 2, 3)))
void Frontend::log(const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  backend->true_log(fmt, ap);
  va_end(ap);
}

void Backend::true_log(const char *fmt, va_list ap) {
  // log the message somehow
}

Если вы хотите использовать макрос, существует обходной путь.

Существуют конструкции, которые заставят компилятор выполнить проверку за вас, но не будут генерировать вызываемый код. Одна такая конструкцияsizeof. Итак, вы можете использовать макрос для своего регистратора, чтобы передать аргументы вprintf прямо, но в контексте sizeof расчет, а затем вызвать сам регистратор.

Причина использования макроса заключается в том, чтобы убедиться, что строка формата обрабатывается так же, как и строковый литерал.

На рисунке ниже я рассматриваю sizeof вычисление как однозначный аргумент, но должны быть другие способы применить ту же технику.

template <typename... Ts>
void Frontend::log(size_t, const char *fmt, Ts&&... args) {
  backend->true_log(fmt, std::forward<Ts>(args)...);
}

#define log(...) log(sizeof(printf(__VA_ARGS__)), __VA_ARGS__)

Попробуйте онлайн!

Конечно, это обходной путь. Есть множество причин не использовать макрос. И в этом случаеlog макрос будет мешать работе любой другой функции или метода с таким же именем.

Другие вопросы по тегам