C ++ decltype и круглые скобки - почему?

32

Тема обсуждалась раньше , но это не дубликат.

Когда кто-то спрашивает о разнице между decltype(a)и decltype((a)), обычный ответ - aэто переменная, (a)это выражение. Я считаю этот ответ неудовлетворительным.

Во-первых, aэто также выражение. Варианты основного выражения включают, среди прочего:

  • (выражение)
  • ID-выражение

Что еще более важно, формулировка для decltype рассматривает скобки очень и очень явно :

For an expression e, the type denoted by decltype(e) is defined as follows:
(1.1)  if e is an unparenthesized id-expression naming a structured binding, ...
(1.2)  otherwise, if e is an unparenthesized id-expression naming a non-type template-parameter, ...
(1.3)  otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, ...
(1.4)  otherwise, ...

Так что вопрос остается. Почему скобки обрабатываются по-разному? Кто-нибудь знаком с техническими документами или обсуждениями в комитете? Явное рассмотрение скобок заставляет думать, что это не упущение, поэтому должна быть техническая причина, по которой я скучаю.

Офек Шилон
источник
2
«обычный ответ - а - это переменная, (а) - это выражение,« что они означают » (a)- это выражение, а aтакже выражение и переменная».
HolyBlackCat

Ответы:

18

Это не недосмотр. Интересно, что в Decltype и auto (revision 4) (N1705 = 04-0145) есть утверждение:

Правила decltype теперь прямо заявляют об этом decltype((e)) == decltype(e)(как предложено EWG).

Но в Decltype (редакция 6): предлагаемая формулировка (N2115 = 06-018) одно из изменений

Выражение в скобках внутри decltype не считается выражением id-expression.

В формулировке нет никакого смысла, но я полагаю, что это своего рода расширение типа decl с использованием немного другого синтаксиса, иными словами, оно предназначалось для дифференциации этих случаев.

Использование для этого показано в C ++ draft9.2.8.4:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 17;        // type is const int&&
decltype(i) x2;                 // type is int
decltype(a->x) x3;              // type is double
decltype((a->x)) x4 = x3;       // type is const double&

Что действительно интересно, так это то, как это работает с returnутверждением:

decltype(auto) f()
{
    int i{ 0 };
    return (i);
}

Моя Visual Studio 2019 предлагает мне удалить лишние круглые скобки, но на самом деле они превращаются в то, decltype((i))какие изменения возвращают значение, int&которое делает его UB с момента возврата ссылки на локальную переменную.

espkk
источник
Спасибо за точное указание происхождения! Мало того, что decltype может быть указан по-другому, оказывается, изначально. Документ rev-6 явно добавляет это забавное поведение в скобках, но пропускает обоснование :(. Я думаю, это так же близко к ответу, как и сейчас ...
Офек Шилон
И все же никто не предложил решить глупую проблему, сделав из нее два разных ключевых слова для двух разных функций? Одна из самых грубых ошибок комитета (которая исторически допускала много необоснованных ошибок).
любопытный парень
13

Почему скобки обрабатываются по-разному?

Круглые скобки не обрабатываются по-разному. Это не заключенное в скобки id-выражение, которое обрабатывается по-другому.

При наличии скобок применяются обычные правила для всех выражений. Тип и категория значения извлекаются и кодируются в типе decltype.

Существует специальное положение, чтобы мы могли легче писать полезный код. При применении decltypeк имени (членской) переменной мы обычно не хотим какой-либо тип, который представляет свойства переменной, когда рассматривается как выражение. Вместо этого нам нужен только тип, с которым объявлена ​​переменная, без необходимости применять тонну свойств типа, чтобы получить ее. И это именно то, что decltypeуказано, чтобы дать нам.

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

Рассказчик - Unslander Monica
источник
Так для intэлемента iиз a, decltype(a.i)это в intто время как decltype((a.i))есть int&(предполагается , что aне const)? Так как выражение a.iприсваивается?
n314159
1
@ n314159 - В этом суть. Выражение a.iявляется неконстантным lvalue, поэтому вы получаете неконстантный lvalue ссылочный тип для (a.i).
StoryTeller - Unslander Моника
Почему «обычные правила для всех выражений» указывают, что их тип является ссылкой? Почему «свойства переменной как выражения» включают свойство ссылки? Это естественный дефолт?
Офек Шилон
1
@OfekShilon - их тип не является ссылкой. Тип выражения никогда не анализируется как ссылка . Но dectlype может разрешить только тип, и он должен сообщить нам не только тип выражения, но и его категорию значений. Категория значений кодируется ссылочными типами. lvalues &, xvalues &&и prvalues ​​не являются ссылочными типами.
StoryTeller - Unslander Моника
@StoryTeller точно, нет «естественной» разницы между типами выражений и не выражениями. Что делает различие искусственным.
Офек Шилон
1

До C ++ 11 языку нужны инструменты для получения информации двух разных типов :

  • тип выражения
  • тип переменной, как это было объявлено

Из-за характера этой информации, функции должны были быть добавлены на языке (это не может быть сделано в библиотеке). Это означает, что новое ключевое слово (ы). Стандарт мог бы ввести два новых ключевых слова для этого. Например, exprtypeчтобы получить тип выражения и decltypeполучить тип объявления переменной. Это был бы ясный, счастливый вариант.

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

Так с C ++ 11 , мы получили только одно ключевое слово , используемое для двух различных вещей: decltype. Различия между этими двумя видами использования заключаются в том, чтобы относиться по- decltype(id-expression)разному. Это было сознательное решение комитета, (небольшой) компромисс.

Болов
источник
Я помню, как слышал это на разговоре cpp. Однако я не надеюсь найти источник. Было бы здорово, если бы кто-то нашел это.
Болов
1
C ++ исторически добавил кучу новых ключевых слов, многие без подчеркивания, а некоторые с одним английским словом. Рациональное действительно абсурдно.
любопытный парень
@curiousguy каждое введенное ключевое слово будет конфликтовать с существующими символами пользователя, поэтому комитет придает большое значение решению добавить новые зарезервированные ключевые слова в язык. Это не абсурдно.
Болов
Не правда: exportбыл представлен. Если вы можете иметь export(ранее все шаблоны были по умолчанию «экспортированы»), вы можете иметь такие вещи, как decltypeи constexpr. Очевидно, что добавление registerна другом языке было бы проблематичным.
любопытный парень
@bolov Спасибо! (1) Это предположение или знание? Знаете ли вы о дискуссиях, в которых мотивировкой было избегание лишних ключевых слов? (2) Можете ли вы привести пример, когда id-выражение на самом деле нужно обрабатывать по-разному, если оно используется «как выражение»? (Что это вообще значит?)
Офек Шилон