170 похожих чатов

Народ, а кто-нибудь знает почему в лябдах нельзя замыкать объявляемую

лямбда-переменную? Простой пример рекурсивной лямбды не работает
int main() {
auto fn = [](){
fn();
};
};
>> clang: variable 'fn' declared with deduced type 'auto' cannot appear in its own initializer
>> gcc: error: use of 'fn' before deduction of 'auto'
И также нельзя замыкать переменные которые объявляются ниже из-за чего не работает пример с косвенной рекурсией
int main() {
auto fn = [=](){
fn2();
};
auto fn2 = [=](){
fn();
};
};
>> clang: use of undeclared identifier 'fn2'
>> gcc: 'fn2' was not declared in this scope;
Кто-нибудь знает почему в стандарте запретили замыкать текущую объявляемую переменную и переменные которые объявляются ниже? И запретили ли вообще? Может это просто недостаток текущей реализации компиляторов а стандарт это не запрещает?

42 ответов

57 просмотров

это делается либо через первый auto& аргумент(рекурсивный вызов лямбды), либо через deducing this C++23

По тем же причинам, почему нельзя struct S { S s; }; - бесконечная рекурсия

Alexander Karaev
По тем же причинам, почему нельзя struct S { S s; ...

рекурсивный вызов лямбды не всегда бесконечный..

Богдан- Автор вопроса
Kelbon
это делается либо через первый auto& аргумент(реку...

я знаю как можно сделать через auto-параметр и y-combinator (на стековерфлоу есть пример) меня интересует почему конкретно этот пример не работает и где это запрещается в стандарте

>> Кто-нибудь знает почему Упрощённо, чтобы вместо auto подставить конкретный тип слева, нужно распарсить определение справа. А как его распарсить, если в нём используется переменная, у которой не выведен тип?

А замыкать то, чего ещё даже нет в скоупе - это уже лютый бред. Конструктор копирования на чём вызывать, если лайфтайм ещё не начался?

Как написал @Kelbon , эта проблема решена в С++23, решать её как-то иначе уже не будут

Богдан- Автор вопроса
Alexander Karaev
А замыкать то, чего ещё даже нет в скоупе - это уж...

если лямбда была вызвана до инициализации переменных которые объявляются ниже то можно выдать ошибку (если это можно статически проанализировать) или сказать что UB

Богдан
если лямбда была вызвана до инициализации переменн...

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

Богдан
если лямбда была вызвана до инициализации переменн...

Верно я понимаю, что вы хотите заставить компилятор заглядывать в код ещё и наперёд?

Богдан
если лямбда была вызвана до инициализации переменн...

Причем тут вызвана-то? Вы предлагаете ещё и конструировать её отложенно? Предлагаю подучить матчасть по лямбдам и перестать предлагать то, что ломает все возможные фундаментальные вещи в языке

Богдан- Автор вопроса
Alexander Karaev
Как написал @Kelbon , эта проблема решена в С++23,...

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

Богдан
ну меня интересует где стандарт запрещает замыкать...

Дело не в том, что стандарт запрещает. Дело в том, что нет вординга, который бы позволял этого. И если вы вдумаетесь в абсурдность постановки задачи вывода типа от лямбды, зависимой от этого типа, зависимого от лямбды, зависимой от ещё невыведенного типа...

Богдан- Автор вопроса
Богдан- Автор вопроса
Ofee Oficsu
Дело не в том, что стандарт запрещает. Дело в том,...

А причем тут сложность реализации вывода типов? Это технические нюансы реализации. Я просто хочу чтобы работали эти два примера и хочу разобраться где в стандарте запрещается такое поведение. Просто если это не запрещено значит это можно будет исправить в компиляторе и С++ перестанет быть аутсайдером потому что в других популярных языках где есть лябды эти два примера работают

Богдан
А причем тут сложность реализации вывода типов? Эт...

Это не технические нюансы. Это нюансы формализации поставленной задачи. Вот прямой запрет в стандарте

Богдан
А причем тут сложность реализации вывода типов? Эт...

Пример с лямбдой столь же абсурден, сколь и более прямолинейный: auto n = n; Вы же не предлагаете разрешить и его?

Богдан- Автор вопроса
Ofee Oficsu
Это не технические нюансы. Это нюансы формализации...

в этой строчке лишь говориться что нельзя объявить имя с переменной у которой не выведен тип, ок, я согласен с этим но что тогда мешает вывести тип лямбды которая замыкает сама себя? Ведь замыкаемая переменная становится полем некой анонимной структуры а стандарт уже разрешает рекурсивные структуры Что такое лямбда? Заходим на https://cppinsights.io/ и видим что этот пример лямбды с замыкаемой переменной int some = 1; auto func = [&]{ std::cout << some; }; раскрывается в такой анонимный класс int some = 1; class __lambda_5_15 { private: int& some; public: __lambda_5_15(int& _some) : some{_some} {} public: inline /*constexpr */ void operator()() const { std::cout.operator<<(some); } }; __lambda_5_15 func = __lambda_5_15{some}; Мы видим что тип замыкаемой переменной стал типом поля в сгенерированном классе, то есть получается что для такого примера (где я добавил одну строчку рекурсивного вызова лябды) int some = 1; auto func = [&]{ std::cout << some; func(); }; мы должны получить такой сгенерированный класс int some = 1; class __lambda_5_15 { private: int& some; private: __lambda_5_15& func; public: __lambda_5_15(int& _some, __lambda_5_15& _func) : some{_some}, func{_func}{} public: inline /*constexpr */ void operator()() const { std::cout.operator<<(some); func.operator()(); } }; __lambda_5_15 func = __lambda_5_15{some}; в котором должно добавиться поле с типом этой сгенерированной лямбды, все логично? А теперь отправляем этот сгенерированный класс компилятору и видим что никакой ошибки нет потому что в плюсах разрешены классы с полями-ссылками которые рекурсивно ссылаются на свой же тип. То есть я не вижу ни одной причины почему рекурсивные лямбды (замыкание объявляемой переменной лямбды) запрещены потому что мы прямо по стандарту следуем правилам раскрытия лябмд в анонимные классы

Богдан
в этой строчке лишь говориться что нельзя объявить...

>> тип лямбды которая замыкает сама себя То, что вам придётся обратиться к оператору() невыведенного типа

Богдан
в этой строчке лишь говориться что нельзя объявить...

В общем, вы пытаетесь апеллировать к реализации замыканий — структурам, а я вам пытаюсь писать о формальной стороне вопроса. Вы хотите для решения своей проблемы использовать неверный инструмент, который потребует внесения очень нетривиальных правок в стандарт языка. При том, что решение проблемы, куда как более верное и не рушащее ничего уже имеется, то решение, что предлагаете вы — сложное, хрупкое, никому не нужное и не попадёт в C++23, в отличие от: auto fn = [](this auto& fn){ fn(); };

Богдан- Автор вопроса
Ofee Oficsu
>> тип лямбды которая замыкает сама себя То, что ...

вот рабочий пример (https://godbolt.org/z/Gxb9YqM17) с анонимным классом который должен сгенерироваться в случае рекурсивной лямбды. Вы согласны с тем что я добавил новую замыкаемую переменную всем правилам раскрытия лямбд в анонимные классы? Так почему тогда рекурсивные лямбды должны быть запрещены если рекурсивные классы у которых поля-ссылки ссылаются на себя разрешены и работают в с++? Ситуация ведь полностью аналогичная

Богдан
вот рабочий пример (https://godbolt.org/z/Gxb9YqM1...

>> с анонимным классом Проблема в том, что лямбда — это не совсем эквивалент анонимного класса с точки зрения грамматики языка. Лямбда — отдельная сущность, описываемая отдельными правилами. Кроме того, вы учитываете лишь частный случай — захват по ссылке. А что с захватом по значению?

Богдан
вот рабочий пример (https://godbolt.org/z/Gxb9YqM1...

неочень то эффективно получается, значит вас язык от неэффективности спас

Богдан
вот рабочий пример (https://godbolt.org/z/Gxb9YqM1...

А теперь исправьте на auto func. Проблема в auto.

Богдан
вот рабочий пример (https://godbolt.org/z/Gxb9YqM1...

>> Так почему тогда рекурсивные лямбды должны быть запрещены если рекурсивные классы у которых поля-ссылки ссылаются на себя разрешены и работают в с++? Ваши примеры грамматически не эквивалентны определению лямбды. Давайте я упрощу всё: template<auto L = []{ L(); }> struct Foo; Куда вы собрались выносить анонимный класс в приведённом примере? У вас нет такого синтаксического инструмента — вы не можете определить класс внутри треугольных скобок. А с лямбдой это возможно. Потому что это совершенно другая языковая сущность, описанная отдельными пунктами стандарта

Богдан- Автор вопроса
Ofee Oficsu
>> Так почему тогда рекурсивные лямбды должны быть...

при чем здесь параметр темплейта? Давайте по порядку. Что такое лямбда? Лямбда это функтор - инстанс некого сгенерированного класса (где определен оператор вызова и поле для каждой замыкаемой переменной) который инициализируется со значениями замыкаемых переменных. То есть рекурсивная лябда которая замыкает сама себя будет представлена классом-функтором у которого поле будет иметь тип этого класса-функтора Мы ведь можем определить тип класса у которого поле будет иметь тип этого же класса? class Functor { Functor& func; operator()(){ func(); } }; Functor func = Functor{some}; Можем. Мы ведь можем создать инстанс класса и в конструкторе передать ему объявленную но непроинициализированную переменную этого же инстанса класса? class Functor { Functor& func; Functor(Functor& _func) : func{_func} {} operator()(){ func(); } } Functor func = Functor{func} Можем. Так почему тогда лямбда которая замыкает себя по ссылке auto func = [&](){ func(); } не работает если это полностью эквивалентно функтору в который раскрывается лямбда?

Богдан
при чем здесь параметр темплейта? Давайте по поряд...

>> Лямбда это функтор - инстанс некого сгенерированного класса Нет, если вы будете честны и посмотрите с точки зрения дизайна языка, то увидите, что первично лямбда-выражения, а то, что оно создаёт функтор — это лишь незначительная деталь реализации И ни один ваш пример не может воспроизвести магических свойств лямбда-выражений, которыми их наделил стандарт, как например возможность определить тип замыкания "внутри" треугольных скобок. Вы не можете описать лямбды, апеллируя к тому, что из себя представляет замыкание. С точки зрения стандарта первично лямбда выражение, а замыкание вторично >> Мы ведь можем определить тип класса у которого поле будет иметь тип этого же класса? Во-первых, нет, не можете, это будет другой тип — ссылка. И это лишь частный пример — вы при этом не учитываете захват по значению. Во-вторых, не важно, можете или нет — то, что лямбда-выражение генерирует объект — это деталь реализации, вы не можете в вопросе дизайна языка апеллировать к детали реализации. Первичен дизайн — формальное описание lambda expressions, а функтор — closure type вторичен, он описывает лишь практическую сторону вопроса и является зависимым от лямбды. Именно так, а не наоборот >> Мы ведь можем создать инстанс класса и в конструкторе передать ему объявленную но непроинициализированную переменную этого же инстанса класса? Осталось понять, ради чего этот уродливый костыль, когда в вашем распоряжении уже есть this >> не работает если это полностью эквивалентно функтору в который раскрывается лямбда? Потому что первичный источник проблемы — процесс вывода типов и невозможность записи лямбда выражений иным способом, а не реализация замыкания, именно об этом вам в чате все твердят с утра. Ваш пример, переписанный честно и без уловок, будет выглядеть вот так: struct Functor { Functor& func; Functor(Functor& _func) : func{_func} {} operator()(){ func(); } }; auto func = Functor{func}; И заметьте, несмотря на все ваши ухищрения и апелляции к структурам — он тоже не работает. Потому что запись лямбды эквивалентна именно такой строчке внизу с точки зрения грамматики языка. А проблема в выводе типов осталась — тип func зависит от типа func. Это абсурд Я бы посоветовал задуматься о глобальном дизайне языка, а не о сиюминутных хотелках, разваливающих его на ровном месте. Если вы лично готовы заняться вордингом — вы вольны его предложить комитету. Но я подозреваю, что никто не захочет читать полотна околоюридического формального текста, содержащего тонны оверинжиниринга, разваливающего (окончательно) стройность языка ради такой мелочи В общем, с точки зрения идей и высоких материй ваша идея не работает — вам придётся серьёзно переписывать стандарт языка, внося в него дополнительные костыли. Если не верите — можете почитать стандарт и попробовать написать вординг С практической точки зрения никому это тоже не нужно — уже есть deducing this, решающий вашу проблему более элегантно и, что важнее, гибко — и передача по значению, и по всем видам ссылок Что мы ещё упускаем, ради чего стоит всерьёз рассматривать вашу идею?

Y комбинатор вам в помощь

Богдан- Автор вопроса
Ofee Oficsu
>> Лямбда это функтор - инстанс некого сгенерирова...

Ладно, я понял - похоже проблема в том что даже имея возможность полностью вывести тип лямбды компилятор выдает ошибку в строчке auto some = ... some ... только из-за того что переменная some используется синтаксически где-то внутри своего инициализатора без разбора можно ли вывести этот тип или нет. Осталось только найти строчку в стандарте которая запрещает использовать auto-переменную внутри своей инициализации или речь все же идет oб ill-formed конструкции из-за undeducible-типа (а самозамыкаемая лябда сюда не попадает потому что ее тип вывести можно)

Ofee Oficsu
>> с анонимным классом Проблема в том, что лямбда...

Для компилятора (по крайней мере в clang) лямбда это обычный анонимный класс, его просто закостылили из до-C++11 "функторов"

Богдан
Ладно, я понял - похоже проблема в том что даже им...

>> Осталось только найти строчку в стандарте которая запрещает использовать auto-переменную внутри своей инициализации Вот же, я выше давал, если я верно поняла контекст этого утверждения из стандарта. И там речь одновременно идёт об обоих утверждениях. Ибо undeduced placeholder type – это и есть auto, в процессе вывода. И использовать имя переменной вы не можете, поскольку не можете name by an expression

Evgeny Sh.
Для компилятора (по крайней мере в clang) лямбда э...

Как я ниже указал – первичен стандарт языка и определение lambda expression в нём. Что из себя представляет closure при этом – это частные детали дизайна языка

Богдан
Ладно, я понял - похоже проблема в том что даже им...

Кстати компилятор мог бы это делать, он например умеет "читать" определения классов в два захода - сначала сканирует все поля и объявления методов, и только потом заходит в определения методов. То есть можно писать sizeof(*this), и вызывать свои методы в месте "раньше", чем находится объявление+определение метода, и т.д.

Evgeny Sh.
Кстати компилятор мог бы это делать, он например у...

>> То есть можно писать sizeof(*this) Это не совсем так. Вы этот this в основном можете использовать только внутри метода. А определение метода как бы после класса идёт, даже если описано внутри него

Богдан
при чем здесь параметр темплейта? Давайте по поряд...

>> Мы ведь можем создать инстанс класса и в конструкторе передать ему объявленную но непроинициализированную переменную этого же инстанса класса? да, передать можем, а использовать почти не можем, доступ к полям в конструкторе разрешен только через (возможно, неявный) this

Alexander Karaev
Как написал @Kelbon , эта проблема решена в С++23,...

Ну теоретически то можно же например добавить аналог this для лямбд, проблемы только с обратной совместимостью, новое зарезервированное слово. Скажем условный self для упрощения рекурсии, не требующий захвата. Тогда можно делать рекурсивный вызов self(1,2), брать ссылку auto& f = self, делать копию auto f = self. Переинстанцировать с другим capture auto f = self[x] или self[x](123).

Dmitry Sokolov
Ну теоретически то можно же например добавить анал...

В С++23 уже добавили this для лямбд (и не только)

Alexander Karaev
В С++23 уже добавили this для лямбд (и не только)

А оно всё ещё умеет cast в c-function pointer с таким capture?

Dmitry Sokolov
А оно всё ещё умеет cast в c-function pointer с та...

capture не добавляли, добавили доступ к this внутри тела лямбды. Каст должен сохраниться, я думаю

Alexander Karaev
capture не добавляли, добавили доступ к this внутр...

Да, про capture я ступил. Но в сигнатуре таки будет тип лямбды, тот же fib не привести к void(*)(int).

Похожие вопросы

Обсуждают сегодня

Ребят в СИ можно реализовать ООП?
Николай
33
~ 2m21s  nix shell github:nixos/nixpkgs#stack ~  stack ghc -- --version error: … while calling the 'derivationStrict' builtin at /builtin/derivation.nix:...
Rebuild your mind.
6
Добрый вечер, Пока не совсем понимаю как наладить общение между телеграм ботом и ПО для работы с сим боксом. По самому боту так понял: - Нужен некий баланс, который можно поп...
Magic
6
Всем доброго вечера. Разрабатываю 32 раз. приложение в Delphi. Столкнулся с тем, что стандартный  TFilestream  не работает с большим файлом > 2 ГБайт (после вызова функции see...
Vadim Gl
16
Всем привет! Имеется функция: function IsValidChar(ch: UTF8Char): Boolean; var i: Integer; ValidChars: AnsiString; begin ValidChars := 'abcdefghijklmnopqrstuvwxyzABCDE...
Евгений
44
добрый день. Подскажите, есть сайт на 1.4.7 и я хочу обновиться, особо ничего не меняя. мне выбирать версию 1.4.35 или третью ветку? и можно ли обновлять "как есть", или нужно...
Digital Cat
12
Кто кодит под Лазарем на винде, у вас аналогично VCL переопределяются CreateWnd и CreateParams для конкретных классов контролов и все заданные флаги влияют?
А Андрей
11
У меня задача: написать брокер сообщений. Очереди и потребители. Очереди поддерживают приоритеты. Очередь отдает сообщения, только обработчикам с соответствующими характеристи...
Aleksandr Filippov
2
народ, плиз хелп, всю голову сломал себе уже... разве может быть так, что GetProcAddress( GetModuleHandle( "kernel32.dll" ), "SetThreadDescription" ) вернёт ненулевое значение...
Iluha Companets
12
А, ты про текущую реализацию? Нет конечно, я бы сделал правильно - сейчас там гавнокод
Александр (Rouse_) Багель
6
Карта сайта