Техника разбора C++ объявлений или покажите мне слона.
От: ZAMUNDA Земля для жалоб и предложений
Дата: 12.03.09 01:25
Оценка: 1 (1)
Блн, пока писал вопрос, походу во всём разобрался. :) Вы всё равно посмотрите пожалуйста: там мелкие вопросы всётаки остались, и я что-то неправильно понять всёравно мог. Плюс ко всему, для меня, до этого момента (надеюсь), этот раздел C++ был китайской грамотой; так что надеюсь я не один такой дурак и информация будет полезной. Ну и жалко просто.
Если баян — простите.
---

Господа хорошие, помогите познать технику разбора C++ объявлений объектов и псевдонимов. Есть где-нибудь описание этого искусства?
Страуструп кое-что прояснил в этом деле, но про технику ничего не сказал, отговорился описанием порождений грамматики. У Липпмана вообще только примеры.

Я вроде как понимаю это, но всё мне кажется что понимаю я так, как представляли слона двое слепых в известной притче.
Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один :) , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.

const int n;
n — "пустой" суффикс, объект "n"
теперь читаю слева направо
const — константный
int — типа int
Итого: константный объект n, типа int
Проверил: АГА!
таким образом, это равносильно
int const n;
Проверил: АГА!

int * ap[];
ap[] — суффикс [], массив имя "ap"
опять слева направо
int — типа int
* — указатель
Итого: массив указателей на int
Проверил: АГА!

const int * pc
pc — "пустой" суффикс, объект "pc"
слева направо
const — константный
int — типа int
* — указатель
Итого: константный указатель на int
Проверил: НЕА! Это указатель на константный int

Читаю Страуструпа, он просто говорит, что обявление это: "спецификатор, базовый тип, объявляющая часть", — а потом расписывает что есть что в объявляющей части:
* — указатель
*const — константный указатель (т.е. указатель, и адрес на который он указывает, изменить нельзя).
...

Значит надо разбить объявление на конструкции: "[<спецификатор>] <тип>", "*[<спецификатор>]", "&", "имя[<суффикс>]" а потом сначала смотреть, что говорит суффикс или его отсутствие, а затем слева направо читать префиксные конструкции.
таким образом:
int * ap[];
ap[] — суффикс [], массив имя "ap"
"int", "*", "ap[]" (это в первую очередь)
"ap[]" — массив, звать "ap"
"int" — тип int, без выкрутасов
* — указатель
Итого: массив указателей на int
Проверил: АГА!

const int an[];
Массив константных int.
Проверил:
const int an[] = {0,1,2};
an[1] = n; // Ошибка, константу нельзя менять.
const int an1[]; // Ошибка, не инициализированная константа.
АГА!

int & *const prn;
Константный указатель на ссылку на int. -- белеберда какая-то, но...
Проверил: АГА! Компилятор сказал: "error: cannot declare pointer to `int&'", -- т.е. та белеберда которую я и думал. :)

СТОП! Как же быть с
int const n;
, у Страуструпа сказано, что спецификатор может быть только перед базовым типом! Или Страуструп что-то не договорил, и "[<спецификатор>]<тип>" <==> "<тип>[<спецификатор>]"? Или это компилятор (MinGW 5.1.4) просто разрешает?

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

Например если я хочу объявить ссылку на массив, то без скобок не обойтись. Т.к. сдесь
int an[] = {0,1,2};
int &ran[] = an;
ran разберается так:
"int", "&", "ran[]" (первым)
"ran[]" — массив, звать "ran"
"&" — ссылка
"int" — тип int
Итого: массив ссылок на int.
Проверил: АГА!

Со скобками:
int an[] = {0,1,2};
int (&ran)[] = an;
ошибка!!!
???
Ай, я массив-то сделал int[3], компилятор ж по инициализатору обо всём дотумкал. Поправил:
int an[] = {0,1,2};
int (&ran)[3] = an;
работает, а разбирается так:
"int", "&ran" (первым), "[]"
"&ran" — ссылка, звать "ran"
"[3]" — массив о трёх элементах
"int" — тип int
Итого: ссылка на массив из трёх int.

Теперь попробую извратиться
int n = 99;
int (const*) cpn = &n;
, м.б. "(const*)" будет понято как "*const", но компилятор что-то страшное выдал. Значит скобки должны в себе содержать некую корректную "объявляющую часть".

Про запятые (т.е. конструкции типа 'int k, l, * pn') я понимаю что после запятой надо мысленно вставить базовый тип с его спецификаторами из первого объявления, чтоб получить то что подразумевается в том что после запятой.
int n, * pn; 
// равносильно
int n; int * pn;

const int cn, & rcn = cn;
// равносильно
const int cn; const int & rcn = cn;
Проверил: АГА! Всё сходится.
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re: Техника разбора C++ объявлений или покажите мне слона.
От: Qbit86
Дата: 12.03.09 07:25
Оценка:
Здравствуйте, ZAMUNDA, Вы писали:

ZAM>Господа хорошие, помогите познать технику разбора C++ объявлений объектов и псевдонимов. Есть где-нибудь описание этого искусства?


Встречал где-то статью про чтение объявлений в Си... Ага, вот она: Reading C type declarations.
Глаза у меня добрые, но рубашка — смирительная!
Re: Техника разбора C++ объявлений или покажите мне слона.
От: Roman Odaisky Украина  
Дата: 12.03.09 08:45
Оценка: +5
Вот моя версия: http://rsdn.ru/forum/message/2694653.1.aspx
Автор: Roman Odaisky
Дата: 16.10.07


Насчет const: для компилятора X const и const X — одно и то же. Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &».
До последнего не верил в пирамиду Лебедева.
Re[2]: Техника разбора C++ объявлений или покажите мне слона
От: Lorenzo_LAMAS  
Дата: 12.03.09 09:31
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Вот моя версия: http://rsdn.ru/forum/message/2694653.1.aspx
Автор: Roman Odaisky
Дата: 16.10.07


RO>Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &».


А какие у тебя в данном примере есть еще варианты расположения const, чтобы тип был одним и тем же???
А так — у меня глаза болят от int const Я левша, может поэтому Люблю const Type, а не Type const.
Of course, the code must be complete enough to compile and link.
Re[3]: Техника разбора C++ объявлений или покажите мне слона
От: Roman Odaisky Украина  
Дата: 12.03.09 10:49
Оценка:
Здравствуйте, Lorenzo_LAMAS, Вы писали:

RO>>Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &».

L_L>А какие у тебя в данном примере есть еще варианты расположения const, чтобы тип был одним и тем же???

Впервые столкнулся с этим несколько лет назад, когда некто, привыкший обозначать константные ссылки как const X &, для случая X = int* написал const int * &, что внесло расхождение между тем, что он хотел, и тем, что он сказал. Но компилятор это проглотил, потому что это был MSVC.

L_L>А так — у меня глаза болят от int const :) Я левша, может поэтому :) Люблю const Type, а не Type const.


Дело привычки. Мне неприятно видеть const X, например. Заодно запись «X const& x» собирает детали реализации в одну кучу, по краям остается главное: x имеет тип X.
До последнего не верил в пирамиду Лебедева.
Re[4]: Техника разбора C++ объявлений или покажите мне слона
От: Qbit86
Дата: 12.03.09 10:59
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Впервые столкнулся с этим несколько лет назад, когда некто, привыкший обозначать константные ссылки как const X &, для случая X = int* написал const int * &, что внесло расхождение между тем, что он хотел, и тем, что он сказал.


Также часто встречается путаница с typedef'ами, имеющая схожую природу. Новички иногда считают, что typedef'ы разруливаются компилятором лексически (т. е. буквальной подстановкой), а не синтаксически. Если же привыкнуть писать квалификатор const справа, то легче раскрывать typedef'ы «в уме».
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Техника разбора C++ объявлений или покажите мне слона
От: ZAMUNDA Земля для жалоб и предложений
Дата: 12.03.09 14:14
Оценка:
Здравствуйте, Lorenzo_LAMAS, Вы писали:

L_L>Здравствуйте, Roman Odaisky, Вы писали:


RO>>Вот моя версия: http://rsdn.ru/forum/message/2694653.1.aspx
Автор: Roman Odaisky
Дата: 16.10.07

Поглядел, стрелки это круто! А как tab'ы вставить? :)
Ты кстати в самом красивом примере не вписал объявления типов, что запутывает (я у себя поэтому int использовал, что б понятно было); и разбирал по словам сразу, а не по "единицам объявления" ("[<спецификатор>] <тип>", "*[<спецификатор>]", "*", ...) или я непонял опять что-то.
Я б это так описал
typedef int ( & ( C :: * ( * const * X ) ( void () ) throw() ) ( char, int () ) const ) [];
//                                   ↑                                                      тип X -- это
//                                     ←
//                                 ↑                                                        указатель на
//                         ↑ ↑                                                              неизменяемое значение типа указатель на
//                       →
//                                       ↑                                                  функция
//                                         ↑                                                параметр функции void()
//                                                   ↑                                      спецификатор функции не бросающей исключений
//                                                           ←                              
//                     ↑                                                                    возвращаемое значение функции, типа указатель на
//                  ↑                                                                       
//                ↑                                                                         член класса C
//              →
//                                                             ↑                            функция
//                                                               ↑                          параметр функции типа char
//                                                                     ↑                    параметр функции типа int(),
//                                                                              ↑           спецификатор функции разрешённой для вызова в неизменяющемся объкте
//                                                                                    ←     
//            ↑                                                                             возвращаемое значение функции, типа ссылка на
//          →
//                                                                                      ↑   
//                                                                                        ←
//      ↑                                                                                 ↑ массив типа int.


И ещё вопрос про "(*obj_name)( void () )" — это читается как "(*obj_name)( void smth())" я правильно понимаю, но ведь переменную типа void создать нельзя (т.е. проининциализировать). Или, что я у Страуструпа прочитал, это на самом деле объявление функции без параметров, но у тебя это место не так описано.

RO>>Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &».

L_L>А какие у тебя в данном примере есть еще варианты расположения const, чтобы тип был одним и тем же???
ГЫ. "Акела промахнулся". :)

L_L>А так — у меня глаза болят от int const :) Я левша, может поэтому :) Люблю const Type, а не Type const.

Я лично, согласен с RO (Roman Odaisky), "<тип> <спецификатор>" лучше, потомучто аналогично синтаксису "* <спецификатор>" и благодаря этому объявление проще читать, например:
obj_type/*+++*/const * */*+++*/const obj_name;
. А лево/право для меня всегда было пофиг, я в децтве пользовался обеими руками, потом в школе правой писать научился. :)
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re[2]: Техника разбора C++ объявлений или покажите мне слона
От: MasterZiv СССР  
Дата: 12.03.09 16:23
Оценка: 1 (1) +2 :)
Roman Odaisky пишет:

Я всегда
> пишу X const, так понятнее, к чему именно относится const в случаях
> вроде «X * const* &».

А я пишу

const X

и никогда не пишу ничего вроде «X * const* &»
Posted via RSDN NNTP Server 2.1 beta
Re: Техника разбора C++ объявлений или покажите мне слона.
От: Аноним  
Дата: 15.03.09 10:40
Оценка:
Здравствуйте, ZAMUNDA, Вы писали:

ZAM>Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.


Читать декларации в C/С++ надо читать справа налево. Так гораздо проще. Посмотрим на твоих примерах.

ZAM>
const int n;
n — "пустой" суффикс, объект "n"

ZAM>теперь читаю слева направо

Читаем справа налево.
Объявляется объект с именем n, имеющий тип инт, константный.

ZAM>
int * ap[];
ap[] — суффикс [], массив имя "ap"

ZAM>опять слева направо

Опять справа налево.
Объявление массива (скобочки) с именем ap, указателей на int.

ZAM>
const int * pc
pc — "пустой" суффикс, объект "pc"


Указатель на константный (неизменяемый) int (нельзя изменять то, на что указывает pc).


ZAM>Читаю Страуструпа, он просто говорит, что обявление это: "спецификатор, базовый тип, объявляющая часть", — а потом расписывает что есть что в объявляющей части:

ZAM>* — указатель
ZAM>*const — константный указатель (т.е. указатель, и адрес на который он указывает, изменить нельзя).

Что значит "нельзя изменить адрес"? Адрес он просто есть. Нельзя изменить значение указателя (которое и содержит адрес).
Re[2]: Техника разбора C++ объявлений или покажите мне слона
От: Lorenzo_LAMAS  
Дата: 16.03.09 06:13
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, ZAMUNDA, Вы писали:


ZAM>>Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.


А>Читать декларации в C/С++ надо читать справа налево. Так гораздо проще. Посмотрим на твоих примерах.


К сожалению, такой способ мало пригоден на сложных рекурсивных деклараторах. Тут уже придется либо метод "вправо-влево" (или как там его), или просто разобраться с грамматикой.
Of course, the code must be complete enough to compile and link.
Re[3]: Техника разбора C++ объявлений или покажите мне слона
От: ZAMUNDA Земля для жалоб и предложений
Дата: 17.03.09 12:58
Оценка: :)
Здравствуйте, Lorenzo_LAMAS, Вы писали:

ZAM>>>Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один :) , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.


А>>Читать декларации в C/С++ надо читать справа налево. Так гораздо проще. Посмотрим на твоих примерах.

Согласен, и в таком случае, прошу переопределить моё ощущение от грамматики C++ декларакций из "китайская грамота" в "арабский манускрипт". :-)

L_L>К сожалению, такой способ мало пригоден на сложных рекурсивных деклараторах. Тут уже придется либо метод "вправо-влево" (или как там его), или просто разобраться с грамматикой.

Вот собсно "разобраться с грамматикой" и небыло особо трудно, трудность там только то, что круглые скобки являются и частью лексемы "параметры функции", и группирующими символами (хотя "группа лексем" это тож лексема). Если я, конечно, правильно понимаю что такое лексема. Ну и ещё к чему относить const и т.п., пожалуй.
Первая проблема была в том, что читать надо справа-налево и про это ни у Липпмана, ни у Страуструпа не написано. У Roman_Odaisky написано но как-то мозговыворачивательно, к тому моменту когда он об этом говорит твой мозг уже перегрелся информацией про поиски идентификатора, открывающие/закрывающие скобки и куда/когда повернуть.
Вторая проблема была в наличии псевдонимов для всего и функционального стиля инициализации переменной. Классический пример: "xxx yyy(zzz);"
typedef int xxx;
typedef char * zzz;

xxx yyy(zzz); // функция yyy, принимающая char *, возвращающая int;
typedef int xxx;
const int zzz = 777;

xxx yyy(zzz); // yyy типа int, инициализируемая 777;

Так что выискивание НЕ имени типа в объявлении, чтоб считать его идентификатором и от него начать читать декларацию не помогает. Надо сначала разобрать скобки, не забывая про "параметры функции", а потом в самой вложеной группе справа искать идентификатор, которого может и не быть. А ещё проще сначала посмотреть декларации всех имён, встречающихся в декларации, а потом уже приступать к разбору.

Таким образом, система получается не налево-направо, а вниз-налево. Т.е. вниз по дереву вложенности и налево по одному уровню вложенности.
char ( & f () ) [N];
Сначала скобки:
char ( & f () ) [N];
------------------- 0
     ---------- 1
           -- параметры функции и распознать это -- самое сложное

А теперь по группам вниз (т.е. начиная с самой вложеной), и каждую налево (справа-налево)
1 — группа_0
 & f ()
   ^^^^ функция, имя "f",
 ^ возвращаемый параметр функции, ссылка на

0 — группа_1 (внешняя)
char ( & f () ) [N]
char {группа_1} [N]
     ^^^^^^^^^^ (сначала "шаг вниз") возвращаемый параметр функции, ссылка на.
                ^^^ массив, размера N,
^^^^ тип char

И всё вместе: "функция, имя "f", возвращаемый параметр функции, ссылка на массив, размера N, тип char", -- вроде по русски

Короче, если так посмотреть, то в своих предыдущих сообщениях я так до правды не дошёл. Надеюсь хоть тут смог, поправьте если косяк.
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re[4]: Техника разбора C++ объявлений или покажите мне слона
От: Lorenzo_LAMAS  
Дата: 18.03.09 07:07
Оценка:
Ой много буков! Ой не асилил! Если для простоты взять объявления С, то сначала ты, двигаясь слева-направо, ищещь нужный тебе идентификатор, он будет _первым_ идентификатором, который не может входить в declarator-specifiers. А дальше можно уже и технику вправо-влево использовать. Для разбора типов параметров может оказаться чуть сложнее, т.к. там может не быть имени, а только тип (и будет то, что вроде как называется абстрактный объявитель в терминологии С). Но тоже не сложно.
Of course, the code must be complete enough to compile and link.
Re[5]: Техника разбора C++ объявлений или покажите мне слона
От: ZAMUNDA Земля для жалоб и предложений
Дата: 19.03.09 14:49
Оценка:
Здравствуйте, Lorenzo_LAMAS, Вы писали:

L_L>Ой много буков! Ой не асилил!

Сорь, весна понимаешь — вдохновение попёрла.
Ну просто три сложности:
1. Читать надо справа-налево.
2. (smth) — может быть и скобками и параметрами функции.
3. Любое, НЕ ключевое слово может быть и именем типа, и именем объекта, и объявляемым (т.е. ещё не быть ващще)

L_L>Если для простоты взять объявления С, то сначала ты, двигаясь слева-направо, ищещь нужный тебе идентификатор, он будет _первым_ идентификатором, который не может входить в declarator-specifiers.

Ну уж нинаю как в C, а в C++ фигушки. #2 сложность это не даёт, т.к. первыми тебе могут встретиться параметры функции, а не видя начала (слева) объявления их иногда не отличить от объявляемого идентификатора. Пример надо?
Поэтому я сначала справа-налево разбираю скобки, а дальше каждую скобку налево, начиная с самой вложеной.

L_L>Для разбора типов параметров может оказаться чуть сложнее, т.к. там может не быть имени, а только тип (и будет то, что вроде как называется абстрактный объявитель в терминологии С). Но тоже не сложно.

Ага, и поэтому я сначала смотрю на скобки.

ЗЫ: Я не спорю, я считаю RO
Автор: Roman Odaisky
Дата: 16.10.07
шикарно всё описал, и я чесна ляпнул ему оценку. Просто его способ для меня слишком трудный. Я выработал свой и, собсно, его и описал — ну на случай если я не один дурак такой. Вопрос если и был, то только один: "Я правильно всё понимаю?".
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re: Вот тебе взгляд на слона от K&R
От: Erop Россия  
Дата: 03.09.09 17:53
Оценка: +1
Здравствуйте, ZAMUNDA, Вы писали:

ZAM>Я вроде как понимаю это, но всё мне кажется что понимаю я так, как представляли слона двое слепых в известной притче.


Есть такое правило для С оно восходит вроде как к K&R.

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

Например.
int i; // i -- такое выражение будет иметь тип int
int ** i; // **i -- будет иметь тип int, *i, тип "указатель на int", ну и т.д.
int i[5]; // i[число] будет иметь тип int, ну и так далее


Соответсвенно, расстановка скобок тоже укладывается в эту всю штуку, так как "приоритеты" те же.
Например
int (*f)( int ); // выражение (*f)( число ) будет иметь тип int, ну и далее раскручиваем


Ну а в С++ появилось несколько неукладывающихся в это правило конструкций. Это ссылки, спецификации исключений и CV-квалификаторы методов.

Зато в рамках этой концепции очень просто понять на что действуют CV-квалификаторы. Они всегда действуют на выражениее СПРАВА от них. То есть
int **const** i; // выражение **i будет представлять из себя сонстантное lvalue


Как-то так. Те, кто изучает С++ при моём участии, обычно рады узнать такое мнемоническое правило... Может и кому ещё пригодится...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.