Легкое правило для чтения сложных объявлений const?

Для чтения сложных указателей указателя существует право-левое правило.

Но это правило не упоминает, как читать модификаторы const.

Например, в простой декларации указателя const можно применять несколькими способами:

char *buffer; // non-const pointer to non-const memory
const char *buffer; // non-const pointer to const memory
char const *buffer; // equivalent to previous declartion
char * const buffer = {0}; // const pointer to non-const memory
char * buffer const = {0}; // error
const char * const buffer = {0}; // const pointer to const memory

Как насчет использования const с указателем объявления указателя?

char **x; // no const;
const char **x;
char * const *x;
char * * const x;
const char * const * x;
const char * * const x;
const char * const * const x;

А что такое простое правило для чтения этих объявлений? Какие декларации имеют смысл?

Используется ли по часовой стрелке/спиральному правилу?

Два примера реального мира

Метод ASTUnit::LoadFromCommandLine использует const char ** для предоставления аргументов командной строки (в источнике llvm clang).

Параметр параметра аргумента getopt() объявляется следующим образом:

int getopt(int argc, char * const argv[], const char *optstring);

Где char * const argv[] эквивалентно char * const * argv в этом контексте.

Поскольку обе функции используют одну и ту же концепцию (вектор указателей на строки для аргументов), а декларации различаются - очевидные вопросы: почему они отличаются? Делает еще один смысл, чем другой?

Предполагается, что модификатор const должен указать, что функция не управляет строками этого вектора и не меняет структуру вектора.

Ответ 1

Модификатор const тривиален: он изменяет предшествующее ему значение, если только ничто не предшествует этому. Итак:

char const* buffer;  // const modifies char
char* const buffer;  // const modifies *

и т.д. Вообще, лучше избегать форм, где ничего не предшествует const, но на практике вы их увидите, так что вам нужно помните, что когда тип const не предшествует, вы должны логически переместите его за первый тип. Итак:

const char** buffer;

на самом деле:

char const** buffer;

то есть указатель на указатель на const char.

Наконец, в объявлении функции a [] после чтения как * раньше. (Опять же, вероятно, лучше избегать этой вводящей в заблуждение записи, но вы увидите это, поэтому вам придется иметь дело с этим.) Итак:

char * const argv[],  //  As function argument

является:

char *const * argv,

указатель на указатель const на char.

Ответ 2

(Попытка сосредоточиться на других аспектах вопроса)

правило для деклараций const должно читать их справа налево, а const изменяет следующий токен. Исключение: в начале объявления const изменяет предыдущий токен.

Существует обоснование этого исключения - для элементарных объявлений const char c поиск некоторых людей более естественный, чем char const c - и это сообщил, что предшествующая форма const char c предшествует окончательному правилу const.

Getopt

int getopt(int argc, char * const argv[], const char *optstring);

или

int getopt(int argc, char * const * argv, const char *optstring);

Это означает, что argv является указателем на const-вектор указателей на неконстантные строки.

Но можно было бы ожидать следующее объявление:

int getopt(int argc, char const * const * argv, const char *optstring);

(указатель на константный вектор на константные строки)

Потому что getopt() не должен изменять строки, на которые ссылается через argv.

Не менее char ** (как используется в main()) автоматически преобразуется в char * const * argv.

Clang

ASTUnit::LoadFromCommandLine(...,  const char **argv, ...);

Это означает, что argv является указателем на неконстантный массив указателей на константные строки.

Снова можно было бы ожидать const char * const *argv по той же причине, что и выше.

Но это более заметно, потому что char ** не конвертирует в const char **, например.

int main(int argc, char **argv) {
  const char **x = argv; // Compile error!
  return 0;
}

дает ошибку компиляции, где

int main(int argc, char **argv) {
  char * const *x = argv;
  return 0;
}

и

int main(int argc, char **argv) {
  const char * const *x = argv;
  return 0;
}

нет.