Есть ли специальное правило для лямбда в случае decltype (auto)?

Если я правильно понял этот ответ и ссылался на стандартный раздел [dcl.type.auto.educt -5], код:

decltype(auto) a = e;

всегда эквивалентно

decltype( e  ) a = e;

Но теперь проблема возникает, если вместо e я помещаю лямбда-выражение в decltype(auto):

decltype(auto) lambda = [](){};

Это, к моему удивлению, с успехом работает как в gcc, так и clang. Причина шока, который я испытал, лежит в стандарте, в котором говорится, что лямбда не должна встречаться в неоцененном операнде [expr.prim.lambda # 2] (внимание мое):

Лямбда-выражение является prvalue, результат результата которого называется закрывающий объект. Лямбда-выражение не должно появляться в неоценимом операнд, в аргументе шаблона, в объявлении alias, в typedef декларации или в объявлении шаблона функции или функции вне его тела функции и аргументов по умолчанию.

Но, как я уже упоминал, пример будет эквивалентен:

decltype([](){}) lambda = [](){};

Приведенный выше код явно явно будет плохо сформирован. Конечно, можно предположить, что оператор [](){} внутри decltype является своеобразной ссылкой, которая на самом деле не является ссылкой, как в случае структурированных привязок, но, возможно, там это специальное правило в стандарте, которое я пропустил, запустив лямбда-инициализацию decltype(auto)?

Ответ 1

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

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


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

Где вы видите, что лямбда появляется в неоценимом контексте?

decltype(auto) lambda = [](){};

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

Теперь ваше замешательство, вероятно, происходит, потому что вы, кажется, думаете, что приведенное выше утверждение эквивалентно

decltype([](){}) lambda = [](){};

Это не так, строго говоря. Если вы посмотрите на язык формулировки, есть небольшая разница (выделено мной):

Если заполнителем является спецификатор типа decltype(auto), T должен быть только заполнителем. Тип, выведенный для T, определяется, как описано в [dcl.type.simple], поскольку хотя e был операндом decltype.

Ключевое слово здесь. Это просто означает, что вывод происходит так, как если бы он был decltype(e), что означает, что правила вывода decltype применяются вместо тех, что для auto для операнда e.

Здесь операнд e действительно является лямбдой, но это полностью законно, потому что в Стандарте указано, что поведение такое же, как если бы вы написали decltype([](){}), что означает правила правил decltype вычет применяется для лямбда. Теперь [expr.prim.lambda]/2 здесь не применяется, поскольку лямбда не находится в необоснованном контексте, поэтому для компилятора фактически использовать decltype([](){}) для вывода типа, что означает, что правила decltype должны использоваться для лямбда.

Конечно, если вы пишете decltype([](){}), программа плохо сформирована, но здесь это не так, как упоминалось выше.

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

По крайней мере, как я это понимаю...