Является ли рекурсивный вызов main из его собственных параметров (злоупотребление sizeof с помощью VLA) стандартом C99?

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

Насколько я знаю, следующий код действителен C99:

#include <stdio.h>
#include <stddef.h>

int main(void) {
    size_t n = sizeof(int[printf("%s", "Hello")]);
}

Я понимаю, что sizeof не оценивает выражение, если оно не является VLA (поскольку оно имеет непостоянный размер).

Однако я не уверен, что этот фрагмент кода действителен C99:

#include <stdio.h>

int main(int argc, char** argv);
int main(int argc, char* argv[sizeof(int[printf("%s\n", "Hello") + main(0, NULL)])]) {}

Запустите его в Compiler Explorer

  • Допустимо ли иметь массив размером sizeof(some VLA)?
  • Допустимо ли иметь объявление типа int main(int, char**) и определение типа int main(int, char*[some size])?
  • И допустимо ли рекурсивно вызывать функцию из ее собственных параметров?
  • Гарантировано ли, что рекурсия произойдет, или компилятор имеет право ее оптимизировать? Если произойдет рекурсия, гарантировано ли, что произойдет переполнение стека?

Я думаю, что создание массива размера sizeof(VLA) допустимо, я не знаю правила, которое сделало бы это недействительным.
Если я правильно помню, char*[some size] как параметр функции распадается на char**, но я не знаю, имеет ли он неправильную форму, потому что объявление/определение не полностью совпадают.
И я понятия не имею, допустим ли рекурсивный вызов main из его параметров или нет, я, очевидно, не нашел ни одного ресурса или примера такого кода. Кроме того, есть ли у main правила рекурсивных вызовов в параметрах, отличные от других функций?
Я предполагаю, что компилятор может не оптимизировать его (я думаю, это не UB), поэтому я также думаю, что переполнение стека произойдет всегда.

Цитаты из стандарта были бы очень признательны, большое спасибо.

Я бы подумал, что запрещенная часть вызывает main. Насколько я понял, рекурсивный вызов main фактически запрещен и не имеет никакого отношения к VLA.

SoronelHaetir 20.05.2024 22:29

@SoronelHaetir В C++ это запрещено. В C такого ограничения нет.

Konrad Rudolph 20.05.2024 22:41

Объявление параметра как массива корректируется так, чтобы параметр стал указателем. При этом выражение размера массива отбрасывается, и стандарт C не содержит явного указания о том, вычисляется ли оно. GCC и Clang исторически расходились во мнениях по этому поводу. Компилятор может не оценить это.

Eric Postpischil 21.05.2024 01:24

Обратите внимание, что вы можете избежать бесконечной рекурсии, используя что-то вроде argc ? this : that, чтобы выбрать, выполнять ли дальнейший рекурсивный вызов или нет.

Eric Postpischil 21.05.2024 01:52

@EricPostpischil Что касается корректировки, компилятор должен, по крайней мере, сначала прийти к выводу, что массив является допустимым объявлением и что он не является неполным типом (ограничения объявления массива «Тип элемента не должен быть неполным или функциональным типом») и что он не является t иметь нулевой размер (объявление массива: «Если размер представляет собой выражение, которое не является целочисленным константным выражением... /--/ «в противном случае каждый раз, когда оно вычисляется, оно будет иметь значение больше нуля.») I' Я не уверен, как компилятор может сделать такой вывод, не оценивая выражение размера массива?

Lundin 21.05.2024 11:56

@Lundin: этого нет в разделе ограничений, поэтому компилятору не требуется его диагностировать. Поведение просто не определено, а это значит, что стандарт допускает любое поведение, в том числе не диагностировать нарушение и не оценивать выражение.

Eric Postpischil 21.05.2024 12:50

@EricPostpischil На самом деле ниже в том же абзаце, который я цитировал (семантика), говорится, что «если выражение размера является частью операнда оператора sizeof и изменение значения выражения размера не повлияет на результат оператора, это не указано, оценивается ли выражение размера». Это во многом так, благодаря настройке массива.

Lundin 21.05.2024 12:53

@Lundin: Это означает, что если у вас есть что-то вроде sizeof (char (*)[foo()]), foo() может быть оценено, а может и нет, потому что это не изменит результат выражения sizeof; это будет размер указателя на массив, независимо от размера массива. Здесь это не тот случай; значение, полученное в результате выражения sizeof, будет меняться в зависимости от размера массива.

Eric Postpischil 21.05.2024 13:36

@EricPostpischil Хм, ладно, да, так что это независимо от того, какой результат sizeof в данном случае передается во внешнее объявление размера массива. Тем не менее текст странный, поскольку подразумевает, что если значение влияет на результат оператора, то выражение вычисляется. Или зачем еще им это писать, если оно в любом случае не определено/не указано.

Lundin 21.05.2024 13:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
9
185
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Допустимо ли иметь массив размером sizeof (некоторые VLA)?

Конечно. При объявлении VLA размер может быть любым выражением. Нет никаких причин, по которым sizeof выражения были бы запрещены.

char new_array[sizeof VLA];

эквивалентно

size_t n = sizeof VLA;
char new_array[n];

Допустимо ли объявление типа int main(int, char**) и определение типа int main(int, char*[some size])?

Да. В объявлении они эквивалентны.

И допустимо ли рекурсивно вызывать функцию из ее собственных параметров?

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

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

Оптимизатор может оптимизировать его, если определит, что это не изменит четко определенный результат. Если на самом деле рекурсия приведет к бесконечной рекурсии, и оптимизатор может это определить, он может оптимизировать все это, поскольку можно предположить, что не будет никакого неопределенного поведения.

«из своих параметров», как в примере в вопросе, а не как при использовании тех же аргументов.

Ry- 20.05.2024 22:01

@Barmar Спасибо за четкий ответ. Я отредактировал, чтобы задать еще один вопрос об оптимизации и о том, что разрешено делать компилятору, не могли бы вы ответить на этот вопрос? Кроме того, я задавался вопросом о рекурсии, потому что думал, что в параметрах функция на самом деле не существует, поэтому, возможно, будут применяться другие правила.

Chi_Iroh 20.05.2024 22:02

Оптимизатору разрешено делать все, что не меняет результат.

Barmar 20.05.2024 22:07

Ой, я неправильно понял, что вы имели в виду под «по своим параметрам». Я думал, вы имели в виду «использование собственных параметров»

Barmar 20.05.2024 22:07

Спасибо за редактирование, теперь оно отвечает на все вопросы.

Chi_Iroh 20.05.2024 22:12

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

John Bollinger 20.05.2024 23:21

Ответ: «Оптимизатор может оптимизировать его, если определит, что он не изменит четко определенный результат»: это либо неправильно, либо неполно, в зависимости от определения «четко определенного». Некоторые соответствующие программы имеют несколько разрешенных вариантов поведения, определенных стандартом C. Например, программы, содержащие вычисления с неопределенной последовательностью, могут иметь несколько разрешенных вариантов поведения. В некоторых программах рекурсию можно оптимизировать, даже если это меняет поведение: от одного разрешенного поведения к другому.

Eric Postpischil 21.05.2024 01:18

@EricPostpischil Если вы сможете придумать лучшее описание в одном предложении о том, когда разрешена оптимизация, я заменю его. В противном случае я не думаю, что здесь место для трактата по теории оптимизации.

Barmar 21.05.2024 02:07

@Barmar: Вопросы с тегами языковой юрист — это место для трактатов по оптимизации.

Eric Postpischil 21.05.2024 02:23

«Оптимизатор может оптимизировать ее, если наблюдаемое в результате поведение будет таким же, как и при неоптимизированной реализации программы».

Eric Postpischil 21.05.2024 02:27

@EricPostpischil Разве это не та же проблема, если есть несколько разрешенных вариантов поведения? Оптимизатор может переключать его с одного на другое.

Barmar 21.05.2024 17:24

@Barmar: Если существует несколько разрешенных вариантов поведения, то программа может быть реализована как программа A с одним поведением или как программа B с другим поведением или, возможно, с несколькими. Утверждение, что оптимизатор может что-то оптимизировать, если результирующее поведение такое же, как и в неоптимизированной реализации программы, позволяет сделать поведение таким же, как у реализации A или таким же, как у оптимизации B, и так далее.

Eric Postpischil 21.05.2024 20:04
Ответ принят как подходящий
  • Допустимо ли иметь массив размером sizeof(some VLA)?

Да.

Основными соответствующими положениями C99 были:


[...] [ и ] могут ограничивать выражение или *. Если они ограничивать выражение (которое определяет размер массива), выражение должно иметь целочисленный тип. Если выражение является константой выражение, оно должно иметь значение больше нуля. [...]

Обычный идентификатор (как определено в 6.2.3), который имеет переменное значение. модифицированный тип должен иметь область действия блока и не иметь связи или функции. объем прототипа. Если идентификатор объявлен как объект с статическая продолжительность хранения, она не должна иметь массив переменной длины тип.

(C99 6.7.5.2/1-2)


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


Если размер является целочисленным константным выражением и тип элемента имеет известный постоянный размер, тип массива не является массивом переменной длины тип; в противном случае тип массива является типом массива переменной длины.

Если размер представляет собой выражение, которое не является целочисленной константой выражение: если оно встречается в объявлении в области прототипа функции, он рассматривается так, как если бы он был заменен на *; в противном случае каждый раз оценено, оно должно иметь значение больше нуля. Размер каждого экземпляр типа массива переменной длины не изменяется во время его продолжительность жизни. Если выражение размера является частью операнда sizeof оператор и изменение значения выражения размера не приведет влияют на результат оператора, неизвестно, влияет ли вычисляется выражение размера.

(С99 6.7.5.2/4-5)


Обратите внимание, что последнее предложение не относится к вашему конкретному случаю; Я включил его для полноты картины, поскольку он предполагает смешивание sizeof и VLA.

Единственное дополнительное требование здесь — чтобы значение было больше 0, но это всегда верно для выражений sizeof (объектов нулевого размера не существует).

  • Допустимо ли иметь объявление типа int main(int, char**) и определение типа int main(int, char*[some size])?

Да.

Основными соответствующими положениями C99 были:


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

(С99 6.7/4)


Обратите внимание, что предполагается несколько объявлений одной и той же функции.


Чтобы два типа функций были совместимыми, оба должны указывать совместимые типы возврата. Кроме того, список типов параметров, если они оба присутствуют, должны договориться о количестве параметров и использовании многоточия терминатор; соответствующие параметры должны иметь совместимые типы. [...] (При определении совместимости типов и составного типа, каждый параметр, объявленный с помощью функции или типа массива, принимается как имеющий настроенный тип и каждый параметр, объявленный с квалифицированным тип считается имеющим неполную версию объявленного типа.)

(С99 6.7.5.3/15)


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


Объявление параметра как «массива типа» должно быть скорректировано до «полного указателя на тип», где квалификаторы типа (если таковые имеются) — это те, которые указаны в [ и ] вывода типа массива.

(С99 6.7.5.3/7)


Эта настройка определяет разницу между вашими char** и char*[some size], где они появляются как типы параметров функции.

  • И допустимо ли рекурсивно вызывать функцию из ее собственных параметров?

Это зависит.

C99 не допускает вызовы необъявленных функций (хотя многие компиляторы принимают их как расширение обратной совместимости), а идентификатор функции не находится в области видимости до тех пор, пока не будет завершено ее объявление. Таким образом, выражение в объявлении функции не может вызвать эту функцию, если ему не предшествует другое объявление той же функции. Обычно это не так, но ваш пример позаботился об этом.

В противном случае, как уже обсуждалось, единственным ограничением формы выражения размера в VLA является то, что оно имеет целочисленный тип. Это не ограничивается контекстом, в котором появляется декларация VLA.

  • Гарантировано ли, что рекурсия произойдет, или компилятор имеет право ее оптимизировать?

Нет.

Основным положением здесь является


Наименьшие требования к соответствующей реализации:

[...]

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

[...]

(С99 5.1.2.3/5)


Это основное положение о том, какие оптимизации могут быть выполнены в целом. Предположим, мы интерпретируем printf() для вывода в файл (и это обычная интерпретация), невозможно оптимизировать printf вызовы, которые вообще выдают какой-либо вывод. Однако компилятору разрешено преобразовать это, например, из рекурсии в цикл.

Если рекурсия произойдет, это гарантировано, что произойдет переполнение стека?

Нет.

Даже если компилятор реализует это как рекурсию, в спецификации языка ничего не говорится о стеке или его переполнении. Это твердо относится к области детализации реализации.

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

Спасибо за подробности об оптимизации, я не знал, что эту рекурсию можно преобразовать в цикл.

Chi_Iroh 20.05.2024 23:27

@Chi_Iroh, любую рекурсию можно преобразовать в цикл и наоборот. Но в данном конкретном случае это преобразование довольно тривиально. Это не всегда так.

John Bollinger 20.05.2024 23:29

@Chi_Iroh Читать Лямбда: идеальный переход

Barmar 20.05.2024 23:30

@JohnBollinger Я понимаю, что не совсем ясно выразился, я имел в виду, что не знал, что оптимизатору разрешено преобразовывать рекурсию в цикл.

Chi_Iroh 20.05.2024 23:39

@Barmar Спасибо за ссылку, мне очень нравятся компьютеры и история программирования.

Chi_Iroh 20.05.2024 23:39

Я думаю, что ОП неявно спрашивает, могут ли или должны ли оцениваться выражения в прототипе функции. Я вижу, что ваш ответ явно не касается этого. Если мы согласны с тем, что они не оцениваются (согласно тексту, который вы цитируете из C99 6.7.5.2/4-5), то последующие вопросы о рекурсии являются спорными, поскольку в опубликованном коде рекурсия отсутствует.

M.M 20.05.2024 23:46

@M.M Ты поднял хороший вопрос. Думаю, я не совсем ясно выразился, потому что второй код, который я написал в качестве примера, очень необычен и озадачивает меня. Если я правильно понимаю вашу точку зрения, выражение printf("hello") + main(0, NULL)' не должно оцениваться? Таким образом, GCC, clang и другие были бы неправы?

Chi_Iroh 20.05.2024 23:59

На самом деле это не хвостовая рекурсия. <expression> + <recursive call> нужно выполнить рекурсию, а затем сложить. Хотя можно преобразовать любую рекурсию в хвостовую рекурсию, это требует некоторого рефакторинга, и я бы не ожидал, что компилятор сделает это автоматически (подумайте о том, как бы вы переписали обычную factorial-функцию с хвостовой рекурсией).

Barmar 21.05.2024 02:04

@M.M, я понял, что вопрос «Я понимаю, что sizeof не оценивает выражение, если оно не является VLA (потому что оно имеет непостоянный размер)», отчасти означает, что OP признает, что оценивается операнд sizeof когда это VLA. Так что нет, я не думаю, что они об этом спрашивают.

John Bollinger 21.05.2024 03:35

@Barmar, OP main() всегда возвращает 0, поэтому программе не нужно выполнять рекурсию перед добавлением. Более того, значение выражения размера в любом случае фактически не используется из-за настройки параметров функции — его нужно только оценить на предмет побочных эффектов. Я думаю, что это не хвостовая рекурсия в соответствии с семантикой абстрактной машины, но нас это интересует только в контексте оптимизации, и оптимизатору не должно быть большого труда признать, что он может применять исключение хвостовых вызовов.

John Bollinger 21.05.2024 04:03

На самом деле, я бы совсем не удивился, если бы представление IL в GCC (например) было действительно хвостовой рекурсией.

John Bollinger 21.05.2024 04:07

@JohnBollinger Чтобы я мог с пользой ответить на ваш последний комментарий, можете ли вы явно указать, согласны ли вы с тем, что в коде, о котором спрашивают int main(int argc, char* argv[sizeof(int[printf("%s\n", "Hello") + main(0, NULL)])]) {}, выражение не оценивается? (Как определено в опубликованной вами стандартной цитате, C99 6.7.5.2/4-5).

M.M 21.05.2024 04:42

Как перед корректировкой компилятор может сделать вывод, что объявление массива не является неполным типом или массивом нулевого размера (и то, и другое требуется деклараторами массива C17 6.7.6.2), если параметр размера не оценивается?

Lundin 21.05.2024 12:02

@M.M, это двусмысленность в спецификации: означает ли тот факт, что настройка типа параметра удаляет выражение, что оно не оценивается? Но общепринятая интерпретация (например, GCC) заключается в том, что оно оценивается. Это интерпретация, которую я предполагаю. Однако корректировка типа параметра означает, что результат оценки остается неиспользованным. Если бы оно вообще не было оценено, то на этом, конечно, дело было бы кончено.

John Bollinger 21.05.2024 14:07

Это адресовано мне, @Lundin? Я никогда не говорил, что выражение размера не оценивалось, и я думаю, из этого ответа достаточно ясно, что я интерпретирую спецификацию так, что оно оценивается. Я сказал, что результат не используется. А также то, что компилятор знает возвращаемое значение этого main() во время компиляции.

John Bollinger 21.05.2024 14:15

@Лундин. В 6.7.6.3/4 прямо сказано, что условие неполного типа проверяется после корректировки. Из стандарта мне неясно, происходит ли проверка размера VLA 0 до или после настройки, но это кажется спорным вопросом, поскольку поведение для размера 0 является UB, диагностика не требуется.

M.M 21.05.2024 23:00

@M.M Да, но чтобы добраться до точки после корректировки, для начала это должно быть допустимое объявление массива, иначе его нельзя будет скорректировать. Таким образом, в первую очередь применяются части объявления массива — по той же причине, по которой мы не можем записывать параметры как int x[][]. Я не понимаю, почему компилятор блокирует это, но в то же время разрешает VLA, размер которого оценивается равным нулю. Очень может быть, что ни язык, ни существующие компиляторы здесь не имеют никакого смысла.

Lundin 22.05.2024 08:41

Правда, @Лундин, ты не можешь? Я думаю, ты недооцениваешь себя. int x[][] нарушает языковое ограничение, тогда как int x[n] с n неположительным вычислением во время выполнения нарушает (только) семантическое правило. Первый может быть обработан во время компиляции, и соответствующая реализация должна его диагностировать. Последний не может быть распознан до момента выполнения, и если реализация хочет утвердительно отклонить его, то для этого она должна создать инструментарий. Это, конечно, можно сделать, но я не понимаю, почему это происходит, когда на самом деле массив не выделяется.

John Bollinger 22.05.2024 15:51

Но на самом деле, @Lundin, я не думаю, что согласен с тем, что для настройки типа параметра требуется, чтобы размер VLA оценивался положительно. Ссылаясь на C17 (хотя C99 говорит то же самое), параграф 6.7.6.2/3 использует форму объявления, чтобы указать, что он объявляет массив. Отдельное семантическое правило (6.7.6.2/5) требует, чтобы выражение размера оценивалось положительно (если оно вообще оценивается), но это не спорно, не заменяет и даже не изменяет /3. И не следует этого делать, потому что, опять же, категория типа должна быть определена во время компиляции, а размерность переменной не может быть вычислена до времени выполнения.

John Bollinger 22.05.2024 16:30

Гарантировано ли, что рекурсия произойдет, или компилятор имеет право ее оптимизировать? Если произойдет рекурсия, гарантировано ли, что произойдет переполнение стека?

В спецификации есть пункт, который гласит:

Если размер представляет собой выражение, которое не является целочисленным константным выражением: если оно встречается в объявлении в области прототипа функции, оно рассматривается так, как если бы оно было заменено на *

С99: 6.7.5.2/5

Это означает, что ваш пример кода эквивалентен

int main(int argc, char* argv[*])]) {}

и что выражение размера вообще не должно оцениваться. Однако кажется, что gcc этому не соответствует.

Область действия прототипа функции предназначена только для объявления функции, которое не является определением. int main(int argc, char* argv[*])]) {} не содержит ничего в области прототипа функции. Объявления его параметров находятся в области блока, связанной с телом основной функции, {}.

Eric Postpischil 21.05.2024 01:27

В стандарте C нет пункта 6.7.5.2.5. В пункте 6.7.2.5 есть абзац 5. Абзацы не следует цитировать так, как у вас, поскольку это не позволяет отличить п. 6.7.6.2 от п. 6.7.6 п. 2.

Eric Postpischil 21.05.2024 01:31

@EricPostpischil Правильный ли §6.7.5.2/5?

Chi_Iroh 21.05.2024 01:38

@Chi_Iroh: Это решает проблему двусмысленности. Использовать ли /, ¶, пробел или что-то еще — вопрос эстетики. Обычно я предпочитаю соответствовать оригинальному источнику в использовании круглых скобок, точек или других знаков, чтобы различать части документа. Стандарт C не использует никакой маркировки для различения абзацев, кроме положения на странице, фактически пробелы отделяются от номеров пунктов, поэтому я просто использую пробел, как в «6.7.2.5 5».

Eric Postpischil 21.05.2024 01:50

Понятно, спасибо за объяснение. Я только что отправил редактирование.

Chi_Iroh 21.05.2024 01:53

Я думаю, вы правы в точном определении «области прототипа функции», но тогда остается вопрос о том, когда и даже нужно ли оценивать выражение. Размеры VLA необходимы только при создании VLA или когда sizeof оценивается на VLA (без параметров), поэтому нет необходимости оценивать его для объявлений параметров функции.

Chris Dodd 21.05.2024 02:13

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

Перегрузка и нестабильность
До C++11 «правило одного определения» нарушалось при инициализации членов класса нестатических и неконстантных переменных. Почему?
Что произойдет, если я вызову allocate_at_least(0) согласно стандарту C++23?
Почему изменяемая лямбда преобразуется в указатель на функцию вместо вызова оператора()?
Могут ли `::` и `*`, образующие тип указателя-члена, происходить из разных расширений макроса или они должны появляться как один токен?
Определено ли вычисление смещений указателей байтов между элементами композиции, не являющимися массивами?
Как проверить, является ли конструктор явно дефолтным
Требуется ли создание экземпляра для неиспользуемого, но инициализированного статического элемента данных const int шаблона класса?
Когда типы замыканий наконец стали структурными типами?
Почему существует разница между оператором и соответствующей функцией-членом?