лямбда-переменную? Простой пример рекурсивной лямбды не работает
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;
Кто-нибудь знает почему в стандарте запретили замыкать текущую объявляемую переменную и переменные которые объявляются ниже? И запретили ли вообще? Может это просто недостаток текущей реализации компиляторов а стандарт это не запрещает?
это делается либо через первый auto& аргумент(рекурсивный вызов лямбды), либо через deducing this C++23
По тем же причинам, почему нельзя struct S { S s; }; - бесконечная рекурсия
рекурсивный вызов лямбды не всегда бесконечный..
я знаю как можно сделать через auto-параметр и y-combinator (на стековерфлоу есть пример) меня интересует почему конкретно этот пример не работает и где это запрещается в стандарте
>> Кто-нибудь знает почему Упрощённо, чтобы вместо auto подставить конкретный тип слева, нужно распарсить определение справа. А как его распарсить, если в нём используется переменная, у которой не выведен тип?
А замыкать то, чего ещё даже нет в скоупе - это уже лютый бред. Конструктор копирования на чём вызывать, если лайфтайм ещё не начался?
Я не про вызов, а про лэйаут объекта
Как написал @Kelbon , эта проблема решена в С++23, решать её как-то иначе уже не будут
если лямбда была вызвана до инициализации переменных которые объявляются ниже то можно выдать ошибку (если это можно статически проанализировать) или сказать что UB
а можно ещё как в смейке, не объявленную переменную дефолтным значением инициализировать, а если его нет, то уб
Верно я понимаю, что вы хотите заставить компилятор заглядывать в код ещё и наперёд?
Причем тут вызвана-то? Вы предлагаете ещё и конструировать её отложенно? Предлагаю подучить матчасть по лямбдам и перестать предлагать то, что ломает все возможные фундаментальные вещи в языке
ну меня интересует где стандарт запрещает замыкать объявляемую лябду (первый пример) и запрещает ли, вдруг это недостаток технической реализации и вывода типов в компиляторах
Дело не в том, что стандарт запрещает. Дело в том, что нет вординга, который бы позволял этого. И если вы вдумаетесь в абсурдность постановки задачи вывода типа от лямбды, зависимой от этого типа, зависимого от лямбды, зависимой от ещё невыведенного типа...
да, точнее дефферить при выводе типов
А причем тут сложность реализации вывода типов? Это технические нюансы реализации. Я просто хочу чтобы работали эти два примера и хочу разобраться где в стандарте запрещается такое поведение. Просто если это не запрещено значит это можно будет исправить в компиляторе и С++ перестанет быть аутсайдером потому что в других популярных языках где есть лябды эти два примера работают
Это не технические нюансы. Это нюансы формализации поставленной задачи. Вот прямой запрет в стандарте
Пример с лямбдой столь же абсурден, сколь и более прямолинейный: auto n = n; Вы же не предлагаете разрешить и его?
в этой строчке лишь говориться что нельзя объявить имя с переменной у которой не выведен тип, ок, я согласен с этим но что тогда мешает вывести тип лямбды которая замыкает сама себя? Ведь замыкаемая переменная становится полем некой анонимной структуры а стандарт уже разрешает рекурсивные структуры Что такое лямбда? Заходим на 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(); };
вот рабочий пример (https://godbolt.org/z/Gxb9YqM17) с анонимным классом который должен сгенерироваться в случае рекурсивной лямбды. Вы согласны с тем что я добавил новую замыкаемую переменную всем правилам раскрытия лямбд в анонимные классы? Так почему тогда рекурсивные лямбды должны быть запрещены если рекурсивные классы у которых поля-ссылки ссылаются на себя разрешены и работают в с++? Ситуация ведь полностью аналогичная
>> с анонимным классом Проблема в том, что лямбда — это не совсем эквивалент анонимного класса с точки зрения грамматики языка. Лямбда — отдельная сущность, описываемая отдельными правилами. Кроме того, вы учитываете лишь частный случай — захват по ссылке. А что с захватом по значению?
неочень то эффективно получается, значит вас язык от неэффективности спас
А теперь исправьте на auto func. Проблема в auto.
>> Так почему тогда рекурсивные лямбды должны быть запрещены если рекурсивные классы у которых поля-ссылки ссылаются на себя разрешены и работают в с++? Ваши примеры грамматически не эквивалентны определению лямбды. Давайте я упрощу всё: template<auto L = []{ L(); }> struct Foo; Куда вы собрались выносить анонимный класс в приведённом примере? У вас нет такого синтаксического инструмента — вы не можете определить класс внутри треугольных скобок. А с лямбдой это возможно. Потому что это совершенно другая языковая сущность, описанная отдельными пунктами стандарта
при чем здесь параметр темплейта? Давайте по порядку. Что такое лямбда? Лямбда это функтор - инстанс некого сгенерированного класса (где определен оператор вызова и поле для каждой замыкаемой переменной) который инициализируется со значениями замыкаемых переменных. То есть рекурсивная лябда которая замыкает сама себя будет представлена классом-функтором у которого поле будет иметь тип этого класса-функтора Мы ведь можем определить тип класса у которого поле будет иметь тип этого же класса? 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 комбинатор вам в помощь
Ладно, я понял - похоже проблема в том что даже имея возможность полностью вывести тип лямбды компилятор выдает ошибку в строчке auto some = ... some ... только из-за того что переменная some используется синтаксически где-то внутри своего инициализатора без разбора можно ли вывести этот тип или нет. Осталось только найти строчку в стандарте которая запрещает использовать auto-переменную внутри своей инициализации или речь все же идет oб ill-formed конструкции из-за undeducible-типа (а самозамыкаемая лябда сюда не попадает потому что ее тип вывести можно)
Для компилятора (по крайней мере в clang) лямбда это обычный анонимный класс, его просто закостылили из до-C++11 "функторов"
>> Осталось только найти строчку в стандарте которая запрещает использовать auto-переменную внутри своей инициализации Вот же, я выше давал, если я верно поняла контекст этого утверждения из стандарта. И там речь одновременно идёт об обоих утверждениях. Ибо undeduced placeholder type – это и есть auto, в процессе вывода. И использовать имя переменной вы не можете, поскольку не можете name by an expression
Как я ниже указал – первичен стандарт языка и определение lambda expression в нём. Что из себя представляет closure при этом – это частные детали дизайна языка
Кстати компилятор мог бы это делать, он например умеет "читать" определения классов в два захода - сначала сканирует все поля и объявления методов, и только потом заходит в определения методов. То есть можно писать sizeof(*this), и вызывать свои методы в месте "раньше", чем находится объявление+определение метода, и т.д.
Мог бы, но у нас си наследие
>> То есть можно писать sizeof(*this) Это не совсем так. Вы этот this в основном можете использовать только внутри метода. А определение метода как бы после класса идёт, даже если описано внутри него
>> Мы ведь можем создать инстанс класса и в конструкторе передать ему объявленную но непроинициализированную переменную этого же инстанса класса? да, передать можем, а использовать почти не можем, доступ к полям в конструкторе разрешен только через (возможно, неявный) this
Ну теоретически то можно же например добавить аналог this для лямбд, проблемы только с обратной совместимостью, новое зарезервированное слово. Скажем условный self для упрощения рекурсии, не требующий захвата. Тогда можно делать рекурсивный вызов self(1,2), брать ссылку auto& f = self, делать копию auto f = self. Переинстанцировать с другим capture auto f = self[x] или self[x](123).
В С++23 уже добавили this для лямбд (и не только)
А оно всё ещё умеет cast в c-function pointer с таким capture?
capture не добавляли, добавили доступ к this внутри тела лямбды. Каст должен сохраниться, я думаю
Да, про capture я ступил. Но в сигнатуре таки будет тип лямбды, тот же fib не привести к void(*)(int).
Обсуждают сегодня