Pull to refresh

Comments 201

Тут во всех примерах лямбда-выражения используются фактически не по прямому назначению, а в качестве затычки вместо отсутствующих в С++ вложенных функций. Так-то лямбда придумана именно затем, чтобы её не надо было связывать с каким-либо именем.

Тем не менее такое использование удобно и распространено.

Удивительно, почему было сразу по-человечески не сделать. Присваивание лямбды – это ведь и есть определение функции.

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

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

В связи с этим вопрос со static, на мой взгляд, лишён содержания. Static в функции должен работать буквально точно так же, как он работает в лямбде (в C++ static в лямбде запрещён, но это ничем фундаментально не вызвано).

в C++ static в лямбде запрещён, но это ничем фундаментально не вызвано

Почему вы думаете, что он там "запрещен"? Это не так.

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

Если лямбда захватывает какие-то внешние переменные, то нет, это и близко не так.

Это не так в C++ именно по той причине, что почему-то в C++ лямбды могут быть вложенными, а определения функций нет. Но в теории это одно и то же.

Но в теории это одно и то же.

В теории комар тоже вертолет. На практике же лямбда не эквивалентна вложенной функции. Лямбда - это синтаксический сахар для структуры, которая содержит operator(), который соответствует коду лямбды, но она содержит не только его, а еще некий набор полей, который соответствует списку захвата лямбды. Вот этого списка захвата вложенной функции и не хватает, она не может его "носить с собой", будучи переданной куда-то вовне для вызова совсем из другого места вне контекста своей внешней функции. Нет сейчас тех переменных внешней функции на стеке, которые использует вложенная функция - все, обломинго, вызывать ее извне нельзя, даже если ты ухитрился куда-то там передать на нее указатель. Лямбда же способна захватывать интересующие ее переменные из стека внешней функции по значению, копируя их в свою структуру, и "носить с собой" куда скажут. Вложенная функция - это недолямбда, жалкое подобие левой руки типа [&] (...) {...}, при наличии полноценных лямбд она не нужна.

Секунду.

  1. Вложенных функций в C++ нет.

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

  3. Во многих языках, наиболее популярным из которых является Питон, а наиболее классическими – Лисп и Scheme, оператор описания функции как раз и является упрощённой формой оператора присваивания лямбды. Питоновский транслятор – так тот прямо-таки выдаёт варнинг на дурной тон кодирования при присваивании лямбды. В Scheme, конечно, такого нет, так как там функция описания функции и реализована через функцию присваивания лямбды.

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

Вложенных функций в C++ нет.

Да. А обычные функции в C++ контекст с собой не носят. И некая воображаемая реализация вложенных функций может носить с собой контекст, а может и не носить. Так что я бы на вашем месте поостерегся заявлять, что "в теории лямбды и вложенные функции - это одно и то же".

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

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

auto foo[...](...) {...}

настолько принципиально лучше такого:

auto foo = [...](...) {...};

что для этого стоит вводить в языке специальную сущность? Вы это серьезно?

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

"Полностью определяться" таким образом он может только если у вас любые переменные находятся в куче под управлением GC или счетчика ссылок, как в питончике, и вы можете захватывать все по ссылке вообще не задумываясь о временах их жизни. В C++ это не так, тут переменные с automatic storage duration живут на стеке, поэтому просто области видимости тут недостаточно, нужен еще синтаксис для определения механизма захвата - производить ли его по ссылке или по значению, причем крайне желательно иметь возможность определять это для каждой переменной в отдельности (как сейчас и сделано в лямбдах). Ну, опять же, если вы продолжаете настаивать на том, что "лямбды и вложенные функции - это одно и то же".

А как это работает в паскале? Да элементарно, вложенные функции и все их переменные существуют только внутри инстанса объемлющей функции. Поэтому никаких вопросов с продолжительностью жизни не возникает, а все внешние переменные захватываются по ссылке на стеке.

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

А как это работает в паскале?

В паскале вложенная функция имеет доступ к стек-фреймам внешних функций. При входе в функцию первые N ячеек стека резервируются под указатели на стек-фреймы родительских функций, через них можно получить доступ к переменным любой ф-ции выше. Это поведение "захардкожено" в x86-инструкциях
ENTER <stacksize>, <level>
и
LEAVE

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

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

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

Я насчитал 3 места, где лямбда передаётся:
std::for_each
std::count_if
std::find_if

Только там нет внешних ссылок.

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

там нет внешних ссылок

Имеется ввиду "нет захваченных переменных"? Тогда и ваш аргумент, о применении вложенных функций, тоже несостоятелен. Можно было бы пользоваться обычной глобальной функцией.

А если посмотреть скомпилированный код

А если компилятор заинлайнит вашу "вложенную функцию", можно говорить, что во вложенных функциях нет смысла?

Можно было бы пользоваться и глобальной функцией, но это имеет в точности такие же недостатки, как немотивированное использование глобальных переменных вместо локальных. А именно, необходимость разруливать имена в глобальном пространстве имён и нарушение локальности кода. Зачем какую-то вспомогательную функцию выставлять наружу? Особенно учитывая, что модульной структуры в C++ тоже нет, только инкапсуляцию методов в классах остаётся применять вместо неё.

считаете, что вот такое условное написание:
auto foo... {...}
настолько принципиально лучше такого:
auto foo = ... {...};
что для этого стоит вводить в языке специальную сущность?

В C# посчитали, что нужно и то, и другое. Там есть и лямбды, и вложенные функции. Там вообще очень много сахара. И это довольно приятно.

Ну удачи им с этим. Тут один гражданин бегает регулярно и призывает добавить в плюсы синтаксический сахар для делегатов по образцу шарпика (что в плюсах закрывается std::function, возможно, в сочетании с теми же лямбдами - по вкусу). Мне сей тред сразу про этого гражданина напомнил почему-то, только там были хотелки одного сахара, а тут другого. А по мне плюсовый синтаксис и так... хм... богатый (некоторые считают, что даже чрезмерно), нет смысла его еще больше раздувать разнообразным сахаром.

Это потому что лямбды применяются редко. Вон в js всех задолбало писать слово function и сделали стрелочный синтаксис.
Если б на плюсах писали бизнес-логику, и 90% кода было бы типа
dbcontext.orders.Where(o => o.ClientAddress.City == mycity).OrderBy(o => o.ClientId).TakeFirst(10).ToList();
накопилась бы критическая масса недовольных квадратными скобками и продавили бы стрелочный синтаксис.
Но видимо, не плюсовое это дело, такие задачи.

Вложенные функции в С++ есть. Лямбда - это именно классическая вложенная функция.
Вложенной функции, разумеется, нужен способ доступа к контексту. Для лямбды в С++ способ доступа к контексту реализован немного по-иному, чем, скажем, в Паскале, но способ реализации такого доступа - это деталь реализации, не играющая никакой роли.

Строго говоря, лямбда - это только тело функции.

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

#include <iostream>

int main()
{
    auto lam1 = [counter = 0]() mutable { return ++counter; };
    auto lam2 = [counter = 0]() mutable { return ++counter; };

    std::cout << lam1();
    std::cout << lam1();
    std::cout << lam2();
    return 0;
}

У них два разных тела, просто эти тела по своему коду одинаковые. И даже при одном теле могут быть разные экземпляры функции с разным состоянием (например, в случае рекурсии).

У них два разных тела, просто эти тела по своему коду одинаковые

А если так. Поведение не изменилось, тело фунции (как скомпилированный код) строго одно:

auto lam1 = [counter = 0]() mutable { return ++counter; };
auto lam2 = lam1;

даже при одном теле могут быть разные экземпляры функции с разным состоянием

И при этом вы не отказываетесь от своего прошлого утверждения "Строго говоря, лямбда - это только тело функции"?

Если lam1 и lam2 - одинаковые лямбды, значит, они взаимозаменяемые, вместо одной можно передать вторую?

Если две переменные одинаковые, значит ли это, что вместо одной можно передать вторую?

В вы смешиваете три разные вещи - саму лямбду, её состояние и переменную, которой она присвоена.

lam1 и lam2 - это не лямбды, а переменные, значениями которых являются лямбды. То есть, по сути, функции.

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

Я не знаю, откуда вы черпаете все эти свои представления, но в C++ такая лямбда:

int foo = 1;
int counter = 2;

auto lam = [&foo, counter](int x) mutable {
  ++counter; foo = x + counter; return foo;
};

является синтаксическим сахаром для примерно следующего кода, плюс-минус:

int foo = 1;
int counter = 2;

class some_fancy_compiler_generated_name {
public:
  some_fancy_compiler_generated_name(int &foo, int counter)
    : _foo(foo)
    , _counter(counter)
    {}
  int operator()(int x)
  {
    ++_counter; _foo = x + _counter; return _foo;
  }
private:
  int &_foo;
  int _counter;
};

auto lam = some_fancy_compiler_generated_name{foo, counter};

Нетрудно видеть, что состояние лямбда получает в момент ее создания (захват по ссылке произведен путем инициализации соответствующего ссылочного поля, захват по значению - копированием текущего значения внешней переменной counter в соответствующее поле структуры). Дальше она с этим своим состоянием может быть куда-то передана или скопирована, и ее копия получит свое, отдельное состояние, которое будет существовать независимо от оригинала. В обоих случаях (и с лямбдой, и с вышеприведенной "ручной" структурой) следующий код:

auto lam2 = lam;

std::cout << lam(1) << std::endl;
std::cout << lam(1) << std::endl;

std::cout << lam2(1) << std::endl;
std::cout << lam2(1) << std::endl;

будет работать совершенно одинаково и выведет:

4
5
4
5

то есть внутренние состояния lam и lam2 относительно первоначального состояния каждой копии изменяются независимо друг от друга. Лямбда и есть ее код плюс ее состояние, эти вещи неотделимы друг от друга, поскольку являются взаимозависимыми компонентами одной и той же структуры некоего (уникального) типа, поэтому вот это:

Строго говоря, лямбда - это только тело функции.

и это:

В само по себе лямбда-выражение не входит ни имя функции, ни её состояние. Хотя, если лямбда-выражение начать выполнять в каком-нибудь контексте, оно получит состояние.

это абсолютно неверное представление, никаким магическим образом лямбда "в момент выполнения" состояние не получает, она получает его в момент создания (или, если угодно, определения). Кстати, не знаю, что вы тут подразумеваете под "именем функции".

Лямбда-выражение - это исходный код в правой части вашего присваивания. Оно создаётся в C++ в момент ввода текста программы в IDE (в динамических языках может создаваться и модифицироваться и на этапе выполнения). А вы говорите всего лишь о создании переменной (функционального типа), которой присваивается лямбда-выражение. А состояние - это вообще третье дело, в чём вы можете убедиться, расписав значения переменных при рекурсивном вызове.

Имя функции в данном случае - это, например, lam1.

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

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

По-моему, вы путаетесь в показаниях. Сначала у вас "лямбда - это только тело функции" (т.е. то, что между фигурными скобками). Теперь у вас это, оказывается, "исходный код справа от знака присваивания". И вопрос о том, когда же все-таки лямбда получает состояние, вы как-то попытались несколько неуклюже (на мой взгляд) обойти, так когда же? Вы говорите "в процессе выполнения в некоем контексте", что конкретно вы вкладываете в это понятие? Для меня выполнение лямбды - это вызов её operator(), так вот лямбда получает свое изначальное состояние вовсе не тогда.

Имя функции в данном случае - это, например, lam1.

lam1 - это не функция, это переменная, содержащая экземпляр класса.

Если вы пишете в C++ определение функции, то лямбда-выражение находится между фигурными скобками. Если вы пишете то, что называется лямбда-выражением в стандарте C++, то оно начинается с квадратных скобок. Содержательно то и другое является лямбда-выражением, а синтаксически оформляется в этих двух случаях немного по-разному.

Я не понимаю, что вы называете "изначальным состоянием", поэтому никак ваш вопрос прокомментировать не могу. Пока лямбда-выражение не выполняется в некотором контексте, состояния у него нет. Не будет выполнения - не будет состояния.

В третий раз вам предлагаю, распишите рекурсивное лямбда-выражение (C++, вроде, позволяет это сделать несколькими способами, либо через косвенную рекурсию через именованную функцию, либо через захват переменной с собственным значением лямбды) и разберитесь с его состояниями. Их будет ровно столько, сколько состоится рекурсивных вызовов.

Я не понимаю, что вы называете "изначальным состоянием", поэтому никак ваш вопрос прокомментировать не могу. Пока лямбда-выражение не выполняется в некотором контексте, состояния у него нет. Не будет выполнения - не будет состояния.

Учитывая вашу причудливую терминологию, опять предлагаю вам уточнить, что конкретно вы подразумеваете здесь перед "выполнением" тогда. Ещё раз, я здесь под "выполнением" лямбды подразумеваю вызов её operator(), а что подразумеваете вы?

Да бог его знает, в результате чего оно будет выполняться. Я не настолько хорошо знаю C++, чтобы дать список.

Да бог его знает, в результате чего оно будет выполняться. Я не настолько хорошо знаю C++, чтобы дать список.

Забавно, то есть вы пишете некие слова, не очень-то понимая их смысл, я правильно понял?

Их будет ровно столько, сколько состоится рекурсивных вызовов.

Опять какой-то набор слов. Ну вот вам пример лямбд с изменяемым внутренним состоянием (увеличивающимся счетчиком), вызываемых рекурсивно и итеративно. И у той и у другой состояние, разумеется, изменится столько раз, сколько было вызовов - счетчик-то увеличивается на каждом шаге, в конце и у той и у другой лямбды счетчик будет равен 10. И сколько здесь по-вашему "состояний" - опять-таки учитывая вашу причудливую терминологию?

Забавно, то есть вы пишете некие слова, не очень-то понимая их смысл, я правильно понял?

То, что я пишу, я прекрасно понимаю, так как излагаю вам теорию программирования.

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

Очень хорошо. Теперь посмотрим на состояние вашей программы в тот момент, когда выполняется оператор return. При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние. Последний инстанс выполняет return, а предыдущие выполняют incr(incr).

Если чуть-чуть по-другому организовать рекурсию и передавать переменную counter через параметр, то у неё одновременно будет 11 разных значений в разных инстансах.

Можно вызовы организовать и параллельно, если вам вдруг “замороженные” рекурсивные инстансы покажутся какими-то недостаточно настоящими. Просто параллелизм сложнее объяснить в семантике C++.

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

А у циклического вашего алгоритма состояние лямбды всего одно, в нём постепенно увеличивается значение переменнойcounter.

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

При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние. Последний инстанс выполняет return, а предыдущие выполняют incr(incr).

Никаких "11 активных инстансов" там не существует. Инстанс лямбда-выражения - это инстанс соответствующей структуры (в данном случае - кусок в памяти, где хранится значение счетчика). Этот инстанс всего один, поэтому и значение счетчика при завершении тела оператора лямбды на каждом уровне рекурсии равно 10. Потому что это один и тот же счетчик.

А у циклического вашего алгоритма состояние лямбды всего одно, в нём постепенно увеличивается значение переменнойcounter.

Я бы сказал, что у вас очень специфическое понимание "состояния лямбды", ибо и в рекурсивном, и в итеративном варианте переменная counter всего одна, и ее значение постепенно увеличивается в одном и том же месте в памяти.

В вашем коде наличие инстансов не так очевидно, потому что вы сделали единственную используемую переменную общей (хотя понятно, что program counter всё равно разный). Но если вы посмотрите мой вариант кода, ссылку на который я привёл в предыдущем сообщении, то там различие между инстансами очевидно.

А ваш код не эквивалентен моему, он не выполняет исходную задачу "увеличить внутренний счетчик лямбды до 10 рекурсивным способом", внутреннее состояние лямбды в нем не меняется. В моем коде строго одна и та же задача выполняется рекурсивным и итеративным способом, а у вас нет, у вас выполняется другая задача, которую вы решили более или менее подогнать под ваши априорные заявления. Вы просто наделали кучу счетчиков на стеке, каждый счетчик ниже (или выше, смотря что за архитектура) по стеку больше предыдущего на единицу. Незачет :)

А ваш код не эквивалентен моему,

Конечно.

он не выполняет исходную задачу "увеличить внутренний счетчик лямбды до 10 рекурсивным способом",

Не понял, кто и когда ставил такую задачу?

внутреннее состояние лямбды в нем не меняется.

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

Не понял, кто и когда ставил такую задачу?

Я поставил такую задачу.

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

Спасибо за предложение, но зачем? Я уже написал так, как мне надо, и таким образом, что это опровергает ваши заявления про 100500 инстансов при рекурсии, а вы и дальше упорно, но неуклюже пытаетесь подгонять решение под свои изначальные безграмотные (уж простите) заявления.

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

Вы написали код, в котором различия между инстансами сложно заметить, так как он не имеет для этого диагностических средств. Это никак не опровергает их наличия, а просто показывает, что вы в своём коде не пользуетесь их множественностью, а влепили одну разделяемую переменную на всех.

При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние.

Может вам C++ изучить прежде чем о C++ рассуждать? Там один объект функтор, ссылка на который передается в вызовы incr.

Если чуть-чуть по-другому организовать рекурсию и передавать переменную counter через параметр, то у неё одновременно будет 11 разных значений в разных инстансах.

То в этом случае counter у вас будет не переменной, а параметром вызова. И, т.к. у вас там 10 вызовов в глубину, на каждом вызове у соответствующего значения параметра counter будет свое значение.

К чему я это объясняю?

А вы объясняете? Такое ощущение, что старательно выплескиваете свое непонимание предмета разговора (а предмет -- это лямбды в C++, а не в сферической теории) и упорно не желаете слушать что вам знающие люди объясняют.

К тому, что лямбда-выражение существует само по себе, а его состояния при выполнении – сами по себе.

А вас не смущает, что классы в C++ существуют сами по себе, а экземпляры (объекты) этих классов при выполнении -- сами по себе?

Я не понял, каким образом то, что вы написали (а всё, что вы говорите про реализацию, технически верно), является возражением мне.

(а предмет -- это лямбды в C++, а не в сферической теории)

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

Я не понял, каким образом то, что вы написали (а всё, что вы говорите про реализацию, технически верно), является возражением мне.

Т.е. когда вы говорите про одновременное существование 11 активных инстансов в месте, где ничего подобного нет, а я вам на это возражаю, то вы не понимаете в чем именно возражение состоит? Реально? o_O

а те – технической интерпретацией лямбд в теории математики. Также как, например, сложение в C++ является конкретной реализацией сложения в теории.

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

В математике, например, сложение двух целых неотрицательных чисел a и b есть вполне конкретный точный результат (a+b), причем такой, что (a+b) >= a и (a+b) >= b.

Тогда как в C++ результат (a+b) запросто может быть таким, что условия (a+b) >= a и (a+b) >= b не будут выполняться. Не говоря уже про то, что в случае знакового int-а результат (a+b) для некоторых значений будет не определен из-за UB.

Так что поосторожнее с переложением теории на конкретный ЯП.

Хорошо, давайте разберём конкретно, что вы написали.

Может вам C++ изучить прежде чем о C++ рассуждать?

Может.

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

Это верно, но я ни слова не писал про объекты и другие детали реализации лямбда-выражений в C++.

Если чуть-чуть по-другому организовать рекурсию и передавать переменную counter через параметр, то у неё одновременно будет 11 разных значений в разных инстансах.

То в этом случае counter у вас будет не переменной, а параметром вызова.

А формальный параметр не является переменной, по-вашему? Если вы так считаете, посмотрите тогда на код здесь. state – это переменная?

И, т.к. у вас там 10 вызовов в глубину, на каждом вызове у соответствующего значения параметра counter будет свое значение.

Всё правильно, именно это я и показывал.

К тому, что лямбда-выражение существует само по себе, а его состояния при выполнении – сами по себе.

А вас не смущает, что классы в C++ существуют сами по себе, а экземпляры (объекты) этих классов при выполнении -- сами по себе?

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

В математике, например, сложение двух целых неотрицательных чисел a и b есть вполне конкретный точный результат (a+b), причем такой, что (a+b) >= a и (a+b) >= b.

Тогда как в C++ результат (a+b) запросто может быть таким, что условия (a+b) >= a и (a+b) >= b не будут выполняться. Не говоря уже про то, что в случае знакового int-а результат (a+b) для некоторых значений будет не определен из-за UB.

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

Может.

Так займитесь. Все полезнее, чем писать ерунду здесь.

Это верно, но я ни слова не писал про объекты

Поздравляю соврамши: "При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние."

Инстанс лябмды в C++ -- это и есть объект. И когда вы говорите о мифических активных инстансах, вы тупо не понимаете, что есть всего один этот инстанс. И, возможно, несколько копий ссылки на него на стеке (что не факт).

А формальный параметр не является переменной, по-вашему?

Каждое значение параметра при каждом вызове -- это отдельная переменная. А не одна с 11 разными значениями как в ваших высказываниях:

Если чуть-чуть по-другому организовать рекурсию и передавать переменную counter через параметр, то у неё одновременно будет 11 разных значений в разных инстансах.

Да еще и одновременно.

lavrov.jpg

Инстанс лябмды в C++ -- это и есть объект.

Нет! Именно это вам надо понять. Инстанс лямбды – это объект плюс контекст вызова.

Каждое значение параметра при каждом вызове -- это отдельная переменная.

Если хотите, можете использовать такую терминологию (хотя она отличается от общепринятой, и вы не сможете её подтвердить, скажем, ссылкой на стандарт C++). Но это никак вам не поможет по существу, потому что фактические состояния инстансов разные независимо от того, одной или несколькими переменными вы назовёте её значение при разных вызовах.

Нет!

Мощно, внушаить.

Именно это вам надо понять.

Зачем? Пока что мое понимание совпадает с тем, что происходит в языке. А именно: лямбда -- это синтаксический сахар для определения а) уникального класса с operator() и, при необходимости, нужным набором полей, и b) автоматического создания инстанса (экземпляра, объекта) этого уникального класса. Т.е. лямбды в C++ всего лишь упрощенный и автоматизированный способ создания функторов (если не брать лямбды без захвата).

Что мне даст какая-то противоположная точка зрения?

Инстанс лямбды – это объект плюс контекст вызова.

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

Но это никак вам не поможет по существу, потому что фактические состояния инстансов разные независимо от того, одной или несколькими переменными вы назовёте её значение при разных вызовах.

Здесь требуется переводчик на русский язык.

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

Впрочем, я с удовольствием послушаю, к какому экземпляру объекта, по вашему мнению, относится переменная state в моём примере, и почему экземпляр один, а значений у state хранится 11 разных.

Впрочем, я с удовольствием послушаю, к какому экземпляру объекта, по вашему мнению, относится переменная state в моём примере, и почему экземпляр один, а значений у state хранится 11 разных.

Можно точную ссылку на ваш пример с каким-то неведомым мне state?

https://godbolt.org/z/Gq6cKPxxa

Так у вас там кроме counter еще и state зачем-то... Ну афигеть.

state в вашем примере -- это локальная переменная, которая создается на стеке. Соответственно, при каждом вызове создается новый экземпляр этой переменной. Отсюда и 11 разных значений.

Ну и кстати, к разговору о том, почему формальный параметр counter из этого вашего примера является новой переменной при каждом вызове. Вот простая иллюстрация: https://godbolt.org/z/PzeEh9rdn

Адреса независимых друг от друга counter собираются и значения по ним изменяются отдельно друг от друга.

Так у вас там кроме counter еще и state зачем-то... Ну афигеть.

Да исключительно потому, что Вы и@KanuTaHнаписали, что формальный параметр counter – это, мол, какая-то ненастоящая переменная.

state в вашем примере -- это локальная переменная, которая создается на стеке. Соответственно, при каждом вызове создается новый экземпляр этой переменной. Отсюда и 11 разных значений.

Браво, бинго! Именно.

Как тогда это соотносится с вашим прошлым утверждением:

Инстанс лябмды в C++ -- это и есть объект. И когда вы говорите о мифических активных инстансах, вы тупо не понимаете, что есть всего один этот инстанс. 

?

Описанная в лямбде локальная переменная – это часть лямбды, не так ли? Значит, и экземпляр лямбды включает экземпляр этой переменной.

Ну и кстати, к разговору о том, почему формальный параметр counter из этого вашего примера является новой переменной при каждом вызове. Вот простая иллюстрация: https://godbolt.org/z/PzeEh9rdn

Вы привели пример всего лишь того факта, что одна и та же переменная размещается в различных адресах памяти в разных обстоятельствах. Переменная и адрес – всё же разные вещи в C++.

Да исключительно потому, что Вы @KanuTaHHнаписали, что формальный параметр counter – это, мол, какая-то ненастоящая переменная.

Думаю, что ув.тов.KanuTaH потребует у вас уточнений самостоятельно. А вот раз вы мне что-то приписываете, то прошу подтвердить это утверждение конкретной цитатой. Где я утверждал, что параметр counter -- это ненастоящая переменная?

Как тогда это соотносится с вашим прошлым утверждением:

Нормально. В исходном примере ув.тов.KanuTaH лямбда incr передавалась по ссылке, а не по значению.

Описанная в лямбде локальная переменная – это часть лямбды, не так ли?

Нет. Локальная переменная -- это локальная переменная внутри operator() того уникального класса, который компилятор сгенерировал для лямбды.

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

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

Думаю, что ув.тов.KanuTaH потребует у вас уточнений самостоятельно. А вот раз вы мне что-то приписываете, то прошу подтвердить это утверждение конкретной цитатой. Где я утверждал, что параметр counter -- это ненастоящая переменная?

Да легко.

То в этом случае counter у вас будет не переменной, а параметром вызова.

Нормально. В исходном примере ув.тов.KanuTaH лямбда incr передавалась по ссылке, а не по значению.

Это имеет какое-то отношение к обсуждаемому вопросу?

Описанная в лямбде локальная переменная – это часть лямбды, не так ли?

Нет.

Давайте с самого начала. Лямбда-выражение в C++ – это буквально то, что представлено синтаксисом lambda epression в стандарте C++, не так ли? То есть:

[ captures ] front-attr (optional) (params ) specs (optional)exception (optional)
back-attr (optional) trailing-type (optional) requires (optional){ body }

Вот это и есть лямбда-выражение. Куда входит и body. А частью body является определение и все использования локальной переменной. Вот просто чисто синтаксически.

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

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

automatic storage duration. The storage for the object is allocated at the beginning of the enclosing code block and deallocated at the end.

Объект сам по себе, а выделение памяти для него само по себе.

То в этом случае counter у вас будет не переменной, а параметром вызова.

Вы выпали из контекста. В примере ув.тов.KanuTaH counter был членом лямбды, т.к. членом переменной incr. Вы же пример переделали и сделали counter параметром вызова.

Это имеет какое-то отношение к обсуждаемому вопросу?

Самое прямое. У ув.тов.KanuTaH есть всего один экземпляр incr, для которого несколько раз вызывается operator() и в эти вызовы передается одна и та же ссылка на incr. Внутри этого экземпляра живет один и тот же conter.

Посему здесь нет никаких 11 инстансов incr.

В отличии от вашего примера, в котором counter для каждого вызова размещается на стеке заново.

Вот просто чисто синтаксически.

Я вот, censored, прямо не знаю как это censored комментировать.

Вот у нас есть определение C++ного класса:

struct Demo {
  int f(int a) {
    int b = a+1;
    return b;
  }
};

Значит ли это, что локальная переменная b внутри Demo::f является частью класса Demo?

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

Ну уж нет, когда вы несете бред, то его разоблачение вполне себе по делу.

Значит ли это, что локальная переменная b внутри Demo::f является частью класса Demo?

Является, конечно, так как она является частью его функции-члена Demo::f, а функция-член является частью класса.

Является, конечно, так как она является частью его функции-члена Demo::f, а функция-член является частью класса.

А "является" она с точки зрения семантики C++, которую мы тут по вашим же словам обсуждаем, или с точки зрения каких-то ваших дремучих абстрактных представлений о том, "как по идее должно быть"? Потому что если вы создадите экземпляр Demo и попытаетесь его скопировать в другое место даже во время выполнения функции f, то b , естественно, никуда скопирована не будет.

Так значение этой переменной не является частью экземпляра объекта. Что никак не противоречит тому, что сама переменная является частью класса.

У вас много путаницы между именами и значениями.

Является, конечно

Простите, с этого момента я пас.

Инстанс лямбды – это объект плюс контекст вызова

Это вообще откуда? Инстанс в общепринятой терминологии - это из ООП, буквально "экземпляр объекта". Применительно к лямбдам в c++ - экземпляр функтора.

А у вас инстанс - это стек-фрейм функции, что-ли?

То есть, в строчке

auto lam = []() { return 42; }

у вас никаких "инстансов" не создаётся (что конфликтует с общепринятой терминологией ООП).

А вот в строчке

int x = lam();

создаётся "инстанс" строго на время выполнения функции.

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

Конечно, здесь слово "инстанс" я использую не в смысле ООП, так как оно применяется не к классу, а к лямбда-выражению.

Относительно терминологии: https://en.wikipedia.org/wiki/Recursion

Это достаточно общепринято в теории вычислений.

Конечно, здесь слово "инстанс" я использую не в смысле ООП, так как оно применяется не к классу, а к лямбда-выражению.

И это очень и очень глупо, т.к. речь идет о конкретно C++ и, дабы не вводить путаницу, хорошо было бы использовать устоявшиеся в C++ термины по назначению. Тем паче, что "лямбда-выражения" в вашем смысле в C++ нет.

Относительно терминологии: https://en.wikipedia.org/wiki/Recursion

Даже там instance используется в двух разных смыслах:

While this apparently defines an infinite number of instances (function values), it is often done in such a way that no infinite loop or infinite chain of references can occur.

(т.е. здесь instance -- это что-то вроде конкретного вызова функции)

this requires some administration as to how far various simultaneous instances of the procedures have progressed

(и здесь instance -- это что-то вроде конкретного вызова функции)

Divide and conquer serves as a top-down approach to problem solving, where problems are solved by solving smaller and smaller instances. A contrary approach is dynamic programming. This approach serves as a bottom-up approach, where problems are solved by solving larger and larger instances, until the desired size is reached.

(а здесь instance -- это что-то вроде "кусков" или "блоков" подлежащих обработке данных).

Хорошо, что мы в итоге договорились о терминологии.

Мне не приходит на ум более подходящее слово для того понятия, о котором я веду речь.

Хорошо, что мы в итоге договорились о терминологии.

Вы с кем договорились? Такое ощущение, что с голосами в своей голове. Я вам показал, что в WIki, на которую вы сослались, термин instance употребляется в двух разных смыслах. Ничего больше, никакой из них я не предлагал для использования в разговоре.

Да и от вас я определения не видел.

Я вам показал, что в WIki, на которую вы сослались, термин instance употребляется в двух разных смыслах

И что из этого? Я вам могу любое слово употребить в двух и даже десяти разных смыслах. Меня @qw1попросил привести пример использования, и я это сделал.

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

И что из этого?

А то, что вы не определили смысл своего "инстанс" и говорить "мы в итоге договорились о терминологии" преждевременно.

Плохо, что мы в итоге не договорились о терминологии.

Он (несколько оппортунистически, я бы сказал) считает, что состояние стека является частью инстанса. Ну предположим, но тогда зачем останавливаться на стеке? Давайте будем считать, что частью инстанса являются, например, и регистры GPU, почему нет? Ведь "помимо состояния самого объекта, есть ещё внешний контекст". Но тогда опять что-то идет не так, ибо вот это утверждение:

А у циклического вашего алгоритма состояние лямбды всего одно, в нём постепенно увеличивается значение переменнойcounter.

внезапно оказывается тоже ложным (кстати, оно оказывается ложным даже при обычном loop unrolling, если считать, что регистры CPU входят в контекст, потому что а почему нет?). Так что в подгонке "под нужный ответ" списка того, что входит в инстанс, а что не входит, необходимо таки вовремя остановиться, тютелька в тютельку. Забавно наблюдать конечно за всем этим, этого не отнимешь.

Он (несколько оппортунистически, я бы сказал) считает, что состояние стека является частью инстанса.

У меня есть ощущение, что человек живет в какой-то своей вселенной. И выходит на связь с нами только из-за того, что в дурдоме опять забыли запаролить WiFi :)

Если вы будете использовать и сохранять вместе с инстансом регистры GPU, то тогда, конечно, и они будут его частью. Граница состояния пролегает очень формально, по фактически используемым объектам и их области видимости. Это прямо из математического лямбда-исчисления тянется, свободные и связанные переменные.

Ну то есть и в циклическом алгоритме при минимальных оптимизациях от компилятора с использованием loop unrolling будет "несколько состояний" лямбд, а не одно, как вы утверждаете, поскольку как минимум IP/PC будет разным при вызове разных лямбд, так? Вы же не будете утверждать, что этот регистр не является "фактически используемым объектом"? По-моему вы сами себя в угол загнали со своими вольными трактовками и попытками подогнать реально происходящие процессы под некую абстрактную теоретическую модель (что, как известно, заведомо невозможно выполнить в точности).

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

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

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

Нет, не будет, поскольку в реальности код может "переезжать" с процессора на процессор, и "область памяти" (если считать регистры таковыми) будет каждый раз физически разной. Видите, в ваших абстракциях сплошные дырки.

мы говорим о семантике программы на языке C++

В рамках семантики языка C++ контекстом лямбды является содержимое объекта-функтора. Это то, что будет скопировано, скажем, при копировании этого объекта-функтора в другое место. Стек, регистры GPU/CPU, и т.п. при этом не копируются. Зачем вы их начали сюда приплетать - бог весть.

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

Я ж только что написал, что мы обсуждаем семантику C++

Тогда повторюсь:

В рамках семантики языка C++ контекстом лямбды является содержимое объекта-функтора. Это то, что будет скопировано, скажем, при копировании этого объекта-функтора в другое место. Стек, регистры GPU/CPU, и т.п. при этом не копируются. Зачем вы их начали сюда приплетать - бог весть.

Вы постоянно прыгаете туда-сюда так, как вам в данный момент удобно. Когда удобно - вы говорите о математических абстракциях или "общей теории архитектуры ВС", когда удобно - о семантике C++. Это не годится.

А Вы вообще понимаете разницу между лямбда функцией, лямбда выражением и обычной функцией? По-вашему получается, что это все одно и то же.
У лямбды есть состояние, которое создается из списка захвата лямбда выражением, оно же и место определения ее тела, то что справа от = в примерах выше. А то состояние, о котором Вы говорите, при рекурсивных вызовах, которое связывается с телом лямбды, так это локальное сотояние на сетке, стековый фрейм, как и у обычной функции. При рекурсивных вызовах будет 11 одновременно существующих стековых фреймов, разных инстансов, как Вы говорите, а при итеративном их тоже будет 11, но не одновременно, а новый стековый фрейм на каждой итерации. А вот состояние лямбды во всех вариантах будет одно.
Лямбда (замыкание, closure) собственно и отличается от обычной функции, по мимо отсутствия имени, наличием этого самого сотояния создаваемого лямбда выражением в точке его определения.
И да, теория и практическая реализация лямбд в разных языках, удивительно, может существенно отличаться. Вам говорят о том, как это реализовано и работает в С++, а Вы, почему-то, упорно это игнорируете говоря, что Вы не спец в С++. Что Вы тут тогда хотиде донести/доказать? К чему эти сотрясения воздуха?

Смотрите, с чего вообще начался тред. Комментатор говорит "нахрен ваши лямбды, если их могли заменить вложенные функции". И действительно, в каком-нибудь лиспе-питоне, функция, объявленная внутри другой функции, получает весь её контекст.

В c++ не так, и весь сыр-бор вокруг того, надо ли здесь такое или остановимся на ручном составлении списков захвата и прочих радостях ручных оптимизаций.

А Вы вообще понимаете разницу между лямбда функцией, лямбда выражением и обычной функцией? По-вашему получается, что это все одно и то же.

Содержательно, функция – это имя, связанное с лямбда-выражением. Хотя с точки зрения описания синтаксиса C++ в стандарте, тело функции по историческим причинам не называют лямбда-выражением. Лямбда-функция – это менее удачный термин, который означает в точности то же самое, что лямбда-выражение.

У лямбды есть состояние, которое создается из списка захвата лямбда выражением, оно же и место определения ее тела, то что справа от = в примерах выше.

Это называется “лексическая область видимости” (lexical scope).

При рекурсивных вызовах будет 11 одновременно существующих стековых фреймов, разных инстансов, как Вы говорите, а при итеративном их тоже будет 11, но не одновременно, а новый стековый фрейм на каждой итерации. А вот состояние лямбды во всех вариантах будет одно.

Когда мы здесь говорили об итеративных вызовах, то имели в виду конкретный пример, приведённый коллегой. Если данный код будет скомпилирован на машине с использованием стека, то стековых фреймов при разных вызовах будет 10 (хотя фактически и в одном и том же месте стека). А вот состояние у лямбды здесь будет всего одно, так как её контекст фактически состоит из разделяемой локальной переменной и разделяемого же счётчика инструкций.

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

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

Поскольку в языке С++ область видимости имён всегда лексическая, а лексический контекст вызова лямбда-выражения может меняться в зависимости от точки вызова, то для реализации имеющих список захвата лямбда-выражений действительно используются лексические замыкания. Однако замыкание не является непременным атрибутом лямбда-выражения в C++ и не является содержательным отличием лямбда-выражения от функции.

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

Я зато, условно говоря, спец в лямбда-выражениях. Хотя и языку C++, в котором не считаю себя спецом, многих мог бы поучить.

Что Вы тут тогда хотиде донести/доказать? К чему эти сотрясения воздуха?

Я человек педантичный и люблю правильное использование терминологии. С которым в программировании вообще беда, а в языке C++ в особенности (достаточно сказать, что в описаниях языка C++ используются два противоречащих друг другу определения термина “переменная”, и никого это особо не смущает). Тут без некоторого погружения в основы не обойтись.

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

Я человек педантичный и люблю правильное использование терминологии.

Но при этом не можете в ходе дискуссии сформулировать определение используемого вами же термина "инстанс".

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

И какой практический смысл в обсуждении "фундаментальных вопросов" в комментариях к сугубо утилитарной статье?

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

Может от этого будет больше пользы.

Здесь же вы пока договорились лишь до того, что локальная переменная в теле метода класса является частью класса. Если "фундаментальные вопросы" в вашем понимании ведут к такому маразму, то может ну их вообще?

Но при этом не можете в ходе дискуссии сформулировать определение используемого вами же термина "инстанс".

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

В философском понимании, инстанс – это онтологический бытийный статус какой-либо сущности. Полегчает нам от этого в техническом смысле? Ни капли. Но это не проблема компьютера, который вообще не оперирует семантикой, а осуществляет чисто синтаксическое преобразование цепочки байтов, и поэтому никаких “инстансов”, “лямбд” или “переменных” для него не существует.

С другой стороны, в ООП под “инстансом” (экземпляром класса) понимают просто область памяти, несущую информацию о конкретном объекте. В этом никакой хитрости нет, но это не то, о чём я пишу.

И какой практический смысл в обсуждении "фундаментальных вопросов" в комментариях к сугубо утилитарной статье?

Если вы считаете, что практического смысла нет, зачем тогда вы этим занимаетесь?

Если вы считаете, что в C++ используется какая-то "неправильная" терминология, а C++ники намеренно искажают какие-то устоявшиеся в некоторой теории понятия, то напишите статью с изложением вашей точки зрения и ссылками на работы, в которых эти самые "термины" и "понятия" вводятся. Может от этого будет больше пользы.

Я считаю, что многие программисты вообще не задумываются об основах того, что делают. И это нормально, особенно в молодости.

А что касается языка C++, то в его описании очень много недоговорённостей, отчего, в частности, тут и там возникает UB.

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

Здесь же вы пока договорились лишь до того, что локальная переменная в теле метода класса является частью класса.

Класс в C++ является просто фрагментом внутреннего представления исходного кода программы (после применения макросов и шаблонов), не больше и не меньше. Поэтому локальная переменная в теле метода класса, безусловно, является частью класса. Точно в том же самом смысле, как в Java она содержится в конкретном файле class. Другое дело, что значение этой переменной не является частью экземпляра класса.

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

Вот вы неугомонный какой.

А вы не блудите мыслею по древу.

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

Вы дали ссылку на Wiki, в которой термин instance использовался для обозначения двух разных вещей. Так что от вас ожидалось хотя бы объяснение в каком именно смысле вы здесь, в этом обсуждении используете "инстанс". Но вы не смогли.

В философском понимании, инстанс – это онтологический бытийный статус какой-либо сущности. Полегчает нам от этого в техническом смысле?

Вам -- не знаю. Для меня это еще одно доказательство того, что у вас с головой нелады. Ибо в данном разговоре достаточно определить "инстанс" -- это синоним экземпляра/объекта (типа "инстанс класса D" как "объект класса D") или же это "конкретный вызов конкретной функции в конкретным фрейм-стеком и конкретными значениями переданных параметров". Или же еще что-то.

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

И это нормально, особенно в молодости.

Конечно, и только вы с высоты своих лет познали истину.

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

Ну да, ну да.

Но и это слишком глубокое рассуждение, пока мы с вами не договорились о более фундаментальной вещи, а именно, что же такое переменная: имя, обладающее значением, или значение, которому присвоено имя?

Нам бы о более приземленных вещах договориться, например: зачем все эти околотеоритические блуждания в комментариях к данной конкретной статье?

Ибо в данном разговоре достаточно определить "инстанс" -- это синоним экземпляра/объекта (типа "инстанс класса D" как "объект класса D") или же это "конкретный вызов конкретной функции в конкретным фрейм-стеком и конкретными значениями переданных параметров". Или же еще что-то.

Видите, вы сами интуитивно понимаете, о чём идёт речь. А зачем-то спорите. Но как определение ваша формулировка не покатит, потому что слова “конкретный вызов” сами по себе не определимы, а стек-фрейма может и вообще не быть.

Нам бы о более приземленных вещах договориться, например: зачем все эти околотеоритические блуждания в комментариях к данной конкретной статье?

Я отвечаю на конкретные обращённые ко мне вопросы.

Видите, вы сами интуитивно понимаете, о чём идёт речь.

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

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

Тьфу, срамота.

а стек-фрейма может и вообще не быть.

А вы допустите. Типа "в случае традиционной реализации на стековой архитектуре это будет вызов со своим конкретным стековым фреймом". Этого могло бы быть достаточно.

Но, так понимаю, проста изложения мыслей и вы проживаете в разных вселенных.

Я отвечаю на конкретные обращённые ко мне вопросы.

Т.е. вышли позвизд покичиться своей ученостью?

вы используете термин "инстанс", у вас спрашивают что вы под этим понимаете, вы не можете дать вменяемое описание вашего использования термин "инстанс".

Я с самого начала написал, что под инстансом понимаю лямбда-выражение в контексте его вызова. Но вы стали требовать определение, которого нет.

Мне не хочется "интуитивно понимать" о чем вы, мне хочется знать конкретно. 

Не всё человеческое знание конкретно. С этим приходится как-то жить, если стоит задача оперирования абстрактными понятиями вроде “лямбда-выражений“.

Типа "в случае традиционной реализации на стековой архитектуре это будет вызов со своим конкретным стековым фреймом". 

Да нет на самом деле никакого инстанса в реализации. Это понятие семантики исходного кода.

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

Я с самого начала написал, что под инстансом понимаю лямбда-выражение в контексте его вызова. Но вы стали требовать определение, которого нет.

Еще раз вам повторю: лямбда в C++ -- это инстанс (т.е. экземпляр, т.е. объект) некоторого автоматически сгенерированного анонимного класса.

Поэтому с точки зрения C++ это ваше заумное "лямбда-выражение в контексте его вызова" -- это вообще что-то странное. Поскольку есть а) экземпляр лямбды и b) есть конкретный вызов лямбды (т.е. вызов operator() у экземпляра лямбды). И вот ХЗ что вы под "инстансом" понимаете.

У вас основная проблема в том

Или в том, что взялся что-то объяснять вам.

Еще раз вам повторю: лямбда в C++ -- это инстанс (т.е. экземпляр, т.е. объект) некоторого автоматически сгенерированного анонимного класса.

Вы здесь просто путаете лямбда-выражение с его значением.

Вы здесь просто путаете лямбда-выражение с его значением.

В теории.

На практике вы путаете теорию (возможно ведомую только лично вам) с практикой языка C++.

Ну откройте стандарт или любое описание C++, там будет конкретно написано, что такое лямбда-выражение.

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

Ну откройте стандарт или любое описание C++, там будет конкретно написано, что такое лямбда-выражение.

Вы тут уже пытались блеснуть цитатой:

[ captures ] front-attr (optional) (params ) specs (optional)exception (optional)
back-attr (optional) trailing-type (optional) requires (optional){ body }

Только вот это относится лишь к синтаксису описания лямбды.

Ну и, например, первая же строчка описания lambda-expression на cppreference.com говорит:

Constructs a closure: an unnamed function object capable of capturing variables in scope.

Ну на cppreference.com вообще много всяких залепух, это ж народное творчество. Но в данном случае они правы, если особо не придираться. Просто “function object” – это не объект в смысле ООП, как вы, возможно, подумали. Там поэтому специально стоит ссылка на википедию.

Ну ёжкинжеж...

В C++ уже давным давно есть устоявшийся термин "function object" (он же функтор -- functor). Вы его можете найти на том же cppreference. Или, например, здесь.

И да, ссылка на Wiki там сделана не для function object-а, а для closure.

Тот факт, что вы отрицаете применимость ООП к C++ными функторам... Ну тут у вас либо опять "горе от ума", либо вообще дефицит этого самого ума.

Расскажите тогда, пожалуйста, каким именно образом объект в смысле ООП “capable of capturing variables in scope”.

В смысле инкапсуляции. Что есть один из трех "китов" ООП.

Если для вас непонятно, то речь идет о том, что в функциональном объекте есть свои члены, которые доступны из operator():

class functor_demo {
  int a_;
  int b_;
public:
  functor_demo(int a, int b) : a_{a}, b_{b} {}
  void operator()() { a_ += 1; b_ += 2; }
  int dummy() const { return a_ + b_; }
};

functor_demo fo{1, 2};

К содержимому fo.a_ и fo.b_ вы снаружи просто так не доберетесь. Что и есть инкапсуляция.

Не-не. Здесь речь идёт об определении closure, и имеется в виду список захвата, который как раз и требует применения лексического замыкания. Вот только к экземпляру класса (объекту в смысле ООП) это не имеет никакого отношения, а является поведением функционального объекта в смысле функционального программирования, которое в C++ целиком реализуется на этапе компиляции и не зависит от оопшного инстанционирования.

Не-не. Здесь речь идёт об определении closure, и имеется в виду список захвата, который как раз и требует применения лексического замыкания.

Здесь -- это где?

Лексическое замыкание делается компилятором. Но, при этом, в C++ оно так же специфическое, т.к. программист должен явным образом описывать что же именно (и как именно) он хочет "замкнуть".

Ну и добавим сюда, что в C++ лямбда может "замкнуть" не только что-то из объемлющего контекста, но и определить собственные "члены" в виде новых объектов, доступных только внутри лямбды (но не локальные переменные в теле лямбды). Вроде вот такого:

int make_value() {...}

auto l = [v = make_value()]() { ... };

Вот только к экземпляру класса (объекту в смысле ООП) это не имеет никакого отношения, а является поведением функционального объекта в смысле

Еще раз: в C++ лямбда -- это экземпляр анонимного класса. Список захвата трансформируется в поля этого класса. Грубо говоря:

int a = ...;
auto l = [a]() {...};

есть ни что иное как:

class __autogenerated_lambda_1 {
  int a;
public:
  __autogenerated_lambda_1(int a_initial) : a{a_initial} {}
  auto operator()() { ... /* здесь имеем дело с __autogenerated_lambda_1::a */ }
};
...
int a = ...;
__autogenerated_lambda_1 l{a};

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

является поведением функционального объекта в смысле функционального программирования, которое в C++ целиком реализуется на этапе компиляции

Поделитесь контактами поставщика вашей дури. Аж завидно.

Здесь -- это где?

Здесь – это в определении термина closure, которое вы процитировали.

Лексическое замыкание делается компилятором. Но, при этом, в C++ оно так же специфическое, т.к. программист должен явным образом описывать что же именно (и как именно) он хочет "замкнуть".

Ну и добавим сюда, что в C++ лямбда может "замкнуть" не только что-то из объемлющего контекста, но и определить собственные "члены" в виде новых объектов, доступных только внутри лямбды (но не локальные переменные в теле лямбды).

Всё это верно.

Еще раз: в C++ лямбда -- это экземпляр анонимного класса.

Давайте выражаться корректно. Никакой “лямбды” в C++ нет. Есть лямбда-выражение, и это ни что иное, как именно выражение [...](...) {...}. А есть ООП-шный объект, который порождается от безымянного класса и является значением лямбда-выражения. Также как есть выражение 2*2, а есть его значение 4. И ещё есть результат применения функтора, являющегося значением лямбда-выражения, к конкретному списку фактических параметров.

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

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

Это тоже верно. Но роль лямбда-выражения не сводится только к генерации функтора, она ещё и лексическая.

Здесь – это в определении термина closure, которое вы процитировали.

Я не цитировал определение closure, не приписывайте мне того, чего я не говорил.

И если речь идет про замыкание, то это и есть та часть "сахара", который дает программисту C++ный компилятор. Во времена C++98 функторы приходилось делать вручную и замыкание можно было соорудить на обычных классах только врукопашную, поэтому речь о closure и не шло. Теперь компилятор позволяет это сделать.

Никакой “лямбды” в C++ нет.

Ну надо же. Ж есть, а слова нет.

Есть лямбда-выражение, и это ни что иное, как именно выражение ... {...}.

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

А есть ООП-шный объект, который порождается от безымянного класса и является значением лямбда-выражения.

О, надо же, до вас дошло.

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

А есть еще и само по себе "применение функтора", т.е. вызов operator(), который может занимать какое-то время и приводить к разным результатам.

И, проблема в общении с вами в том, что вы не можете вменяемо обозначить что из всего этого многообразия у вас называется "инстанс".

Но роль лямбда-выражения не сводится только к генерации функтора, она ещё и лексическая.

Лексическая роль пусть волнует разработчиков компиляторов, статических анализаторов и IDE. Мне, как простому пользователю языка, интересно то, чем лямбда является в итоге.

Я не цитировал определение closure, не приписывайте мне того, чего я не говорил.

Вы привели ссылку на следующее определение в cppreference:

Lambda expressions (since C++11)

Constructs a closure: an unnamed function object capable of capturing variables in scope.

Здесь “an unnamed function object capable of capturing variables in scope” – это и есть просто определение closure.

Не то, чтобы я был так уж во всём согласен с определением лямбда-выражения в C++ как представления замыкания, но как рабочее определение без особых претензий – вполне сойдёт.

Это выражение на уровне синтаксиса.

Так ничего, кроме синтаксиса, по самому большому счёту и не нужно. Вот уважаемый @qw1 уже изучил теорию и справедливо написал, что всё применение функций можно вывезти чисто на синтаксисе, простыми подстановками. А всякие классы и объекты – это просто детали реализации.

И, проблема в общении с вами в том, что вы не можете вменяемо обозначить что из всего этого многообразия у вас называется "инстанс".

Из этого многообразия у меня ничего не называется “инстанс”. Этот вопрос не имеет отношения к ООП.

Здесь “an unnamed function object capable of capturing variables in scope” – это и есть просто определение closure.

Как по мне, так это не определение closure, это описание того, что создается в результате обработки лямбда выражения компилятором C++. А именно -- функциональный объект. Который может быть замыканием, а может и не быть.

Так ничего, кроме синтаксиса, по самому большому счёту и не нужно.

Не нужно для чего? Если для того, чтобы продемонстрировать свою банальную эрудицию в сраче на Хабре, то да, больше ничего не нужно.

Если для того, чтобы без проблем пользоваться лямбдами в C++, то это только-только самое начало. Поскольку синтаксис ничего не говорит о том, почему же тогда лямбды могут использоваться в шаблонах. Вроде:

template<typename L> void f(L &&l) {...}

и какой смысл здесь играет "universal reference".

Или вот такое:

std::unique_ptr<ExtType, decltype([](ExtType * p) { freeResource(p); })> holder{...};

Вот уважаемый @qw1 уже изучил теорию

Он изучил то, чего раньше не знал. Я специально лямбда-исчисление не изучал, но наслышан. И это одна из причин почему я вынужден снова и снова макать вас в известную субстанцию и повторять: речь идет о лямбдах в C++, о сугубо практических вещах, перестаньте натягивать на глобус какую-то абстрактную теорию.

А всякие классы и объекты – это просто детали реализации.

Ну ёжкинжеж... Гладко было на бумаге, да, как известно что забыли.

Из этого многообразия у меня ничего не называется “инстанс”.

Тогда что такое "инстанс"?

"Имя, сестра! Имя!" (с)

Этот вопрос не имеет отношения к ООП.

И то хорошо.

Как по мне, так это не определение closure, это описание того, что создается в результате обработки лямбда выражения компилятором C++. А именно -- функциональный объект. Который может быть замыканием, а может и не быть.

Мы идём по кругу. Если это описание объекта ООП, то у него нет способности “of capturing variables in scope“.

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

А почему бы им не использоваться в шаблонах? C++ – язык со статической типизацией, вот и приходится оборачивать выражения в шаблоны для полиморфизма. В отличие, например, от Objective C. Это вообще никакого отношения не имеет к вычислимости выражений.

Если это описание объекта ООП

Это не описание объекта ООП. Это описание того, что получается в результате работы компилятора. А получается объект. К которому применимы некоторые признаки ООП. А посему термин "инстанс" здесь вполне уместен, но именно что в смысле "экземпляр". Поэтому когда вы лезете в разговор о C++ со своим абстрактным "инстансом", то это вызывает недоумение и просьбы уточнить что вы имеете в виду.

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

А почему бы им не использоваться в шаблонах?

Потому что в шаблонах используются не "выражения", а типы или значения. А вот этот вот ваше "лексическое" определение лямбды в виде [...](...){...} ничего на сей счет не говорит.

В отличии от.

Давайте выражаться корректно. Никакой “лямбды” в C++ нет.

Мдяааа, я плакаль...

у вас спрашивают что вы под этим понимаете, вы не можете дать вменяемое описание вашего использования термин "инстанс"

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

Не буду писать, как у них принято
λ xy.yx
потому что боюсь напороть фигни, буду использовать псевдокод.

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

Рассмотрим функцию

fn add(x,y) {
    return x+y;
}

Чтобы её "вычислить", они буквально подставляют значения переменных в тело, текстовой заменой. То есть, add(1,2) раскрывается в

{
    return 1+2;
}

Это я так понял, и есть "инстанциирования", а одна подстановка - инстанс.

Теперь интереснее, про лямбды и замыкания.
В функции могут быть так называемые "свободные пеменнные"

fn foo(x,y) {
    return (a+x)*y;
}

тут a - свободная пеменная.

Допустим, функция foo находится в таком контексте ф-ции bar

fn bar(a) {
    fn foo(x,y) {
        return (a+x)*y;
    }
    return foo(2,3);
}

Нетрудно понять, что при вызове bar(7) переменная внутри foo перестанет быть свободной, а станет константой, и это будет инстансом внутренней лямбды.

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

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

Глубоко копнули, уважаю.

Но всё же одно замечание. Это:

они буквально подставляют значения переменных в тело, текстовой заменой. То есть, add(1,2)раскрывается в

{
  return 1+2;
}

Это я так понял, и есть "инстанциирования", а одна подстановка - инстанс.

называется бета-редукцией, и к инстансам прямого отношения не имеет. Это чисто синтаксический приём.

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

Но правда ваша в том, что всё идёт от лямбда-исчисления.

Тогда я теряюсь в догадках, что же такое "инстанс" ((

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

Нет никакой такой формальной вещи, в которую можно ткнуть пальцем и сказать: вот, это функциональный инстанс. Но тем не менее, когда рекурсивная функция выполняется 11 раз (что бы это ни значило), у неё есть 11 “чего-то“, и это и есть функциональные инстансы. Абстрактно. Не думая о машинном коде, стеке и фреймах.

А бета-редукций ведь может у лямбда-выражения и совсем не случиться (например, если параметров нет).

К это термину вообще применимо понятие "11 раз"?
Как я понял, тут нет времени и 1+2 и 3 это одно и то же.
Число "бета-редукций" считать тоже бессмысленно, потому что на некоторых выражениях (Ω-комбинатор один из примеров), в зависимости от порядка их применения, мы можем ходить вокруг да около, и прийти к одному результату за разное число редукций (либо вообще зациклиться). Такое значение, как "Минимальное число редукций, приводящее к минимальной длине результата", вероятно, в общем случае алгоритмически невычислимо.

В формализме лямбда-исчисления инстансов точно нет.

Это человеческое интуитивное понимание семантики вычислительного процесса. Тут как со множеством, неопределимое понятие.

Мы же понимаем, что рекурсия от 0 до 10 выполняется 11 раз? Даже если бы никакого компьютера не было, просто как чистое понятие о семантике вызова функции? Могли бы и учёному времён Гаусса это объяснить, не грузя его понятием процессора и машинным кодом. Может быть, в то время бы даже лучше поняли, придав этому какое-нибудь богословское толкование, типа того, что Бог явлен в трёх инстансах (и это точно не экземпляры).

Я вроде понял, о чём он говорит.

Скорее всего вы правы.

Я попробую вставить свои 5 копеек, хоть и плохо знаю c++.

При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние.

Не соглашусь, что у каждого из которых своё состояние.

Пример: https://godbolt.org/z/n6hvsed1f

Через механизм Deducing this (ссылка) можно получить объект лямбда функции (точнее результат лямбда-выражения – closure type) в параметр (в примере: self).

Получив адрес в каждом вызове функции, в примере видно, что он одинаковый. Потому что объект/структура типа closure type (далее просто: лямбда), создается компилятором один раз вместо определенного нами лямбда-выражения, а захваченные при замыкание значения идут через конструктор лямбды и сохраняются как приватные поля. Таким образом, мы присваиваем состояние 1 раз.

Из второго адреса можно увидеть, что в примере с замыканием counter находится по тому же адресу, что и лямбда, как первое поле. В примере, который был предложен Вами, но с изменениями, можно увидеть, что counter имеет новый адрес на каждый вызов лябмды, потому это параметр лямбды (ClosureType::operator()(params)).

Ещё один пример: https://godbolt.org/z/54e63s91q

Из данного примера можно увидеть, что copy constructor (ссылка) вызывается 1 раз, при объявлении лямбды (лямбда-выражения), в независимости от количества вызовов лямбды! Потому что передается в конструктор лямбды 1 раз и сохраняется в приватном поле лямбды.

P.S.: Про closure type из документации с cppreference [lambda]:

The lambda expression is a prvalue expression of unique unnamed non-union non-aggregate non-structural class type, known as closure type, which is declared (for the purposes of ADL) in the smallest block scope, class scope, or namespace scope that contains the lambda expression.

P.P.S.: Как я понял из комментариев, Вы, скорее всего, имеете ввиду под "инстанс" вызов функции. Но всё же стоит точно Вам обозначить значение термина. И желательно использовать устоявшиеся термины в среде программирования под c++, а не математики или каких-либо теорий и других языков программирования.

Можно я встроюсь в вашу дискуссию. Обнаружил довольно забавное работающее "нечто". Что думаете? Легально ли это? (clang и msvc кушают)

auto* var = new decltype([](){ std::cout << "IMALIFE"; }){};

Ну, не вижу конкретной причины, по которой это было бы нелегально. Кстати, это не должно работать при непустом списке захвата - по крайней мере в его текущем виде с вызовом дефолтного конструктора (недосуг сейчас проверять).

Это с точки зрения теории

Теперь я понимаю причину наших разногласий. На жаргоне "создать лямбду", "передать лямбду" обозначает действия над функтором, т.е. функциональным объектом. В математике, лямбда-выражение, оно же просто "лямбда" - строка символов, записанная по определённым грамматическим правилам, и вы на
это определение ссылаетесь, когда говорите, что лямбда это только тело функции. Но позже снова переходите на программисткий жаргон, говоря так

lam1 и lam2 - это не лямбды, а переменные, значениями которых являются лямбды

или тут

При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние

Ну какое может быть состояние у "тела функции", какие инстансы?

Вы уж придерживайтесь какой-то одной терминологии. А то прыгаете с одного на другое, так вас понять вообще невозмжно.

Когда мы говорим про “лямбда-выражение”, то в принципе можем в контексте нашей беседы подразумевать три вещи, каждая из которых совершенно формальна.

  1. Определение “лямбда-выражения” в стандарте C++.

  2. Лямбда-выражение в семантике программирования.

  3. Лямбда-выражение в математике.

Я говорю о втором понятии. Первое отличается от второго в основном тем, что по историческим причинам тело обычной функции не называется в C++ лямбда-выражением, хотя семантически им является.

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

При этом одновременно существует 11 активных инстансов лямбда-выражения, у каждого из которых своё состояние

Ну какое может быть состояние у "тела функции", какие инстансы?

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

Это всё достаточно запутано в языке C++, но в функциональных языках предельно ясно:

> (define lam1 (lambda (x) x))

> lam1
#<procedure #2 lam1>

> (pp lam1)
(lambda (x) x)

> (lam1 123)
123

Здесь мы видим, что значение переменной lam1 – это скомпилированный код лямбда-выражения, но мы можем обратиться к этому значению и как к исходному тексту этого кода, например, через функцию pretty print (pp). Так как мы всё-таки занимаемся программированием компьютера, то нам, в отличие от математиков, не уйти от двойственности исходного и фактически выполняемого процессором кода.

Так, стоп. Функция в питоне это совсем не то, что в с/с++. В питоне это объект, которому можно насовать полей, фактически замыкание, а не классическая функция. Собственно лямбда и является аналогом :-)

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

Понятно. Подумал, "сделать по-человечески" значило ввести в язык вложенные функции.

Технически всё, что может сделать функция со static-переменными, может сделать и mutable-лямбда с неограниченным временем жизни. Поэтому в гипотетическом С++ без явных функций и методов, а только с лямбдами (и их неявным методом) вообще не нужны статические переменные внутри лямбд. Но исторически C++ как нашлепка над С-высокоуровневым ассемблером не имел шанса сразу таким появиться. Потому что содержание вопроса со static в том, где какие данные хранятся, который в функциональном программировании по определению вынесен за скобки.

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

Подумал, "сделать по-человечески" значило ввести в язык вложенные функции.

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

Лямбды позволяют сделать больше, чем вложенные функции, благодаря захвату контекста, как объясняет KanuTaH в соседней ветке. Если добавлять захват вложенным функциям, как вы там же предлагаете, получатся те же лямбды, только всегда именованные. Но в реальном C++ функция и объект с operator() — сущности разных категорий. Зачем запутывать язык особым типом функций, вложенными, которые на самом деле являются такими объектами? Сделали просто синтаксический сахар для объектов, не вводя новых сущностей.

И макросов заодно стало меньше.

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

Речь идет про объекты в терминах стандарта C++, а не принципов ООП. Объект принципиально отличается от функции тем, что имеет тип и хранится где-то (*). Это нужно лямбдам, чтобы хранить захваченный контекст. Захватывать контекст требует типичное применение лямбды, когда она вызываются не из той функции, которая лямбду создает (например, создается при старте IO, а вызывается из event loop).

Лямбда сделана именно объектом класса, а не объектом какого-то нового для C++ вида, чтобы не умножать сущности. Про инкапсуляцию у такого объекта, кстати, можно и говорить: интерфейс фиксирован, все данные-члены скрыты.

(*) Да, технически код функции хранится в памяти, но с точки зрения стандарта C++ можно только получить указатель, через который можно вызвать функцию, но нельзя оперировать её кодом как находящимся где-то блоком байт.

Захваченный контекст не является каким-то хранимым объектом или блоком памяти, это чисто лексическое понятие, по сути – указания парсеру компилятора об интерпретации имён переменных. Значение захваченной переменной хранится в том месте, где она первоначально объявлена или аллоцирована, а не в памяти объекта-функтора.

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

Да что же вы такое несете?

https://wandbox.org/permlink/PHtTWQypppHytz0r

Здесь c в лямбде захвачено по значению. И c из лямбды должно где-то хранится.

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

Если вы посмотрите ассемблерный код для вашей программы, то увидите, что никаких экземпляров объектов там фактически не порождается. Проще вам будет разобраться, если вы уберёте iostream.

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

А что такое "локальная память" функции? Стек?

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

А если так: https://wandbox.org/permlink/AX8YzQKz7xsmHYXF

А что такое "локальная память" функции? Стек?

Например, стек. Конкретно в вашем предыдущем примере для x86_64 – переменная списка захвата c , локальная для лямбда-выражения, хранится в стековом фрейме, принадлежащем функции main, рядом с переменной c самой функции main.

А если так

Разумеется, рано или поздно вы сможете извлечь какую-нибудь пользу от существования в C++ неполноценного класса-функтора, порождающего экземпляр в качестве значения лямбда-выражения. Речь идёт о том, что эти класс и объект не являются ни концептуально, ни технически необходимыми.

Весь их смысл состоит в том, чтобы соответствовать парадигме C++ “всё есть объект” (хотя ей и так не соответствуют обычные функции и примитивные типы). Ну и можно из-за этого делать всякие экзотические вещи вроде коллекции функторов. Но это весьма сомнительная выгода по сравнению с потерей единообразия функций и лямбда-выражений.

Например, стек.

Например не катит, хватит уже строить из себя великого теоретика. Лямбда в C++ может быть вызвана несколько раз. Где именно будет храниться ее состояние между вызовами? Стек не подойдет в общем случае.

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

Ой, какие эпитеты, какая патетика.

Те, кто нормально программируют на C++ (а не строят из себя самого умного в комментариях на Хабре) постоянно извлекают полезную пользу из того факта, что лямбда в C++ -- это функтор.

Речь идёт о том, что эти класс и объект не являются ни концептуально, ни технически необходимыми.

Это уже разговоры о том, что и как могло бы быть у бабушки, чтобы это была уже не бабушка, а скорее дедушка. Но в C++ сделали именно так, получилось и просто, и понятно, и удобно (по крайней мере по меркам C++).

Весь их смысл состоит в том, чтобы соответствовать парадигме C++ “всё есть объект”

Еще раз попрошу координаты вашего дилера. Уж очень крутые вещества он вам поставляет. В C++ нет такой парадигмы.

Например, стек.

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

А общего ответа нет.

Лямбда в C++ может быть вызвана несколько раз. Где именно будет храниться ее состояние между вызовами? Стек не подойдет в общем случае.

Я ж вам написал – в памяти, предоставленной функцией, вычислившей значение лямбда-выражения. В приведённом вами примере это стековый фрейм функции main, так как значение лямбда-выражения хранится в локальной для main переменной l , имеющей автоматический класс памяти.

Более поучительно рассмотреть такой пример и сравнить с таким.

А общего ответа нет.

А вот представляете, в том, что воплощено в жизнь в C++, этот общий ответ таки есть! Невероятно для вас, не так ли?

А размещение на стеке без копирования куда-либо оказывается всего лишь приятным следствием оптимизации.

Вот ведь какая реальность. Сурово она с вами.

Я ж вам написал – в памяти, предоставленной функцией, вычислившей значение лямбда-выражения.

Это тот же самый стек. Который не применим, если лямбда должна пережить вызов создавшей её функции.

Более поучительно рассмотреть такой пример.

Я вам привел более поучительный пример выше с использованием копирования лямбд и хранения их в контейнере. Вы явно его не поняли. Разберитесь сперва с ним.

Если вы думаете, что в вашем примере лямбда способна пережить создавшую её функцию main, то вы просто плохо понимаете семантику языка C++. Попробуйте переименовать вашу функцию main и вызвать дважды.

Вот так для вас достаточно наглядно: https://wandbox.org/permlink/fPZtYE5VtpKvWc9l
?

Можете еще обратить внимание, что f и g принимают аргументы по-разному.

Что вы хотите проиллюстрировать этим примером? В данном случае, так как вы погрузили свои лямбды внутрь самых настоящих объектов, наследующих элементам std::vector, то в этих объектах и распределяется память. Потом её ещё и освобождать надо. Это не реализация лямбд, как таковых, а тот способ, которым вы их используете.

Что вы хотите проиллюстрировать этим примером?

Что лямбда в C++ спокойно переживает тот контекст, в котором она была создана.

В данном случае, так как вы погрузили свои лямбды внутрь самых настоящих объектов, наследующих элементам std::vector

Что, черт возьми, ты здесь несешь? Сами-то хоть поняли, что сказали?

Лямбда и есть объект. У этого объекта есть тип. Поэтому можно создать std::vector для экземпляров такого типа. И заполнить этот вектор.

Здесь нет никакого наследования.

Потом её ещё и освобождать надо

Не-а. Но это же C++ знать нужно ну хоть чуть-чуть.

Это не реализация лямбд, как таковых, а тот способ, которым вы их используете.

Это реализация лямбд в C++ позволяет мне использовать их вот таким образом.

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

Напишите конкретный код. Асинхронная операция может использовать бесхозное значение только по недиагностированной ошибке.

https://wandbox.org/permlink/Z2TVJjyUW6Hnr00G
Никаких бесхозных значений. Лямбда в make_request() захватывает this по ссылке и request_id по значению. Затем она сохраняется в std::function. Но поскольку std::function конструируется на основе лямбды, это значит, что захваченное содержалось внутри лямбды, то есть, она не была функцией. Никакого "лексического" захвата быть не может, потому что std::function реализуется чисто средствами C++, и реализация её конструктора не может проинспектировать, что из make_request() использует лямбда — конструктор видит только объект-функтор и знает его размер.

Ясное дело, что, если вы используете std::function, то вам нужен объект-функтор. Тело лямбды при этом само по себе не знает, откуда взялась выделенная ему локальная память.

Тело лямбды при этом само по себе не знает, откуда взялась выделенная ему локальная память.

Так и обычная функция не знает откуда взялась память для ее локальных переменных. Что объясняет эта ваша фраза?

Что способ выделения памяти (и, в частности, место размещения захваченных по значению переменных) зависит от способа использования значения лямбда-выражения.

Что способ выделения памяти (и, в частности, место размещения захваченных по значению переменных) зависит от способа использования значения лямбда-выражения.

Та, собственно, как и любого другого значения, любого другого типа.

Конечно.

Вы утверждали, что всё захваченное лямбдой хранится в каком-то месте, не связанном с лямбдой, и поэтому лямбды могли бы быть не объектами (у которых есть место хранения), а функциями (у которых нет места хранения). Мой пример опровергает это: 1) захваченные лямбдой по значению переменные не существуют в момент вызова лямбды, 2) захваченные копии хранятся внутри std::function, но для создания такого std::function нужен объект с этими данными внутри. Следовательно, лямбды не могут быть функциями в общем случае, потому что приведен случай, где они должны быть объектами.

Конечно, для того, чтобы запихнуть лямбду в std::function, она должна быть объектом. Я этого не отрицал. Но это говорит только о том, что вам в определённых ситуациях нужно представить лямбду как объект, чтобы воспользоваться интерфейсом для объектов. Для этого по сути неявно используется включение. Можно было бы осуществлять такое включение явно, используя объект класса-функтора с конструктором от необъектной лямбды, и ничего бы в реализации не изменилось. А вот с функциями высших порядков дело обстояло бы проще.

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

Я бы сказал, что во всех ситуациях применения лямбд в С++. Все остальное, это какие-то ваши домыслы о том как могло бы быть.

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

Поясните пожалуйста этот набор слов. Что такое "включение" и "необъектная лямбда"? Это вы снова вводите новую "павильную", на ваш взгляд, терминологию?

Включение – это один из основных механизмов ООП, на минуточку. Подразделяется на композицию и агрегацию.

В данном случае речь идёт об агрегации простого тела функции или лексического замыкания в объект (экземпляр класса).

объект класса-функтора с конструктором от необъектной лямбды

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

А вот с функциями высших порядков дело обстояло бы проще.

Для этого нужно функции сделать объектами (в терминах стандарта C++). Но зачем, если C++ позволяет иметь объекты с поведением, как у функций, и даже с более общим, чем у функций.

Чаще всего захватывают значения, а не ссылки. c++ позволяет и то, и другое. Да и конфуз выйдет, если

Значение захваченной переменной хранится в том месте, где она первоначально объявлена или аллоцирована

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

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

Это обычная для C/С++ проблема висячих ссылок.

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

А захват лишь хитрый трюк, чтобы подставить значения, не перекомпилируя код. И захват по значению чище с этим справится, ближе к теории. Так вы почему-то настаиваете на захвате по ссылке. Нет ссылок - нет висящих ссылок ))

Ближе к теории – не переприсваивать значения переменным (как вы уже верно подметили). Тогда захват по значению не будет по своему эффекту отличаться от захвата по ссылке.

Висящие ссылки и иммутабельность/переприсваивания - ортогональные аспекты.

Если ссылка не может измениться, она не может в том числе и стать висячей.

Легко может стать висячей, если объект на котрый она указывает разрушен, а ссылка еще жива. Изменяемость/неизменяемость самой ссылки никак на этот факт не влияет.

Если вы под ссылкой понимаете только само значение указателя, то да. Но всё-таки обычно под ссылкой подразумевают именно доступ к тому объекту, на который она указывает. Возвращаясь к теме беседы, если объект, на который указывает ссылка, не может быть переприсвоен (т.е. в том числе и разрушен), и сам указатель не может быть переприсвоен, то висячая ссылка не образуется.

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

Спасибо за разъяснение, но это и так очевидно.

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

Да, под адресом на конверте подразумевают адрес на конверте, а не дом по этому адресу.

Спасибо за разъяснение, но это и так очевидно.

Ну так вы спорите именно с этим.

Да, под адресом на конверте подразумевают адрес на конверте, а не дом по этому адресу.

Не надо путать адрес со ссылкой, хотя C++ к такой путанице и склоняет.

Ну так вы спорите именно с этим.

Вот именно что с этим, а не с тем что вы позже написали, указав кучу ограничений в которых ваше первое утверждение становится справедливым. Вам не кажется, что это манипуляция, нет?

Не надо путать адрес со ссылкой, хотя C++ к такой путанице и склоняет.

Я про адрес на конверте говорил. А адрес в памяти, да, это аспект реализации ссылок и указателей в С++ и ссылки не обязаны быть адресом.

если объект, на который указывает ссылка, не может быть переприсвоен (т.е. в том числе и разрушен)

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

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

Так, концептуально, иммутабельный объект может быть уничтожен, или нет?

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

То есть, нельзя сказать, что иммутабельные объекты концептуально защищены от недействительных ссылок.

Я этого и не говорил. Про иммутабельные объекты - это ваша тема.

Вам напомнить?

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

Ну да. А вы написали про другое - про концептуально уничтожимые иммутабельные объекты.

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

Мне не очень нравится вообще применение термина “иммутабельность” в этой ситуации, потому что оно означает вполне конкретный атрибут (const) в языке C++, который формально никак не связан со ссылками (а именно, в C++ есть указатели на иммутабельные объекты, иммутабельные указатели на объекты, иммутабельные указатели на иммутабельные объекты, ссылки на иммутабельные объекты, но нет иммутабельных ссылок).

Сама концепция уничтожения объекта в практическом отношении востребована в языке C++ только из-за особенностей используемого архаичного механизма ручного управления памятью. В теории, если понимать под ней математические основы вычислений, никакого уничтожения объектов нет. Висячие ссылки по определению являются артефактом уничтожения (или, что гораздо сложнее себе практически представить, перемещения в адресном пространстве) объектов. Поэтому в вашей постановке вопроса всё зависит от того, какое конкретно понимание вы вкладываете в данный момент в термин “иммутабельность”.

Я в это вкладываю запрет на изменение объекта.
Как мы договорились, разрушение объекта не является его изменением.

В таком случае это разные понятия, с чем, собственно, никто и не спорил.

Эх, никак не могу себя приучить к "видишь глупость - пройди мимо, пусть остаются в невежестве". Тут я конкретно зацепился за "если объект, на который указывает ссылка, не может быть переприсвоен (т.е. в том числе и разрушен), и сам указатель не может быть переприсвоен, то висячая ссылка не образуется". Я подумал: "какого чёрта, как связаны иммутабельность и висячие ссылки, неужто если объекту разрешить безобидные изменения типа x.Color = "red", это что, приведёт к висячим ссылкам? Не согласен я с этим! И начал доказывать". Оппонент же (у него в голове наверняка всё по другому, наверняка думает "вот пристал дурачок") начал вместо признания своей ошибки или приведения аргументов по теме, начал вбрасывать ещё 3 других тезиса, уводя в другую сторону.
Ладно, я понял, что мне на самом деле эти срачи не нужны. Зря я их затеял.

Конечно зря. В частности, зря вы из:

объект, на который указывает ссылка, не может быть переприсвоен (т.е. в том числе и разрушен)

делаете вывод:

 как связаны иммутабельность и висячие ссылки

при условии, что

Я в это [иммутабельность] вкладываю запрет на изменение объекта.Как мы договорились, разрушение объекта не является его изменением.

Иммутабельность с разрушимостью – ваша концепция, а не моя. Она, безусловно, имеет опору на язык C++, но не имеет отношения к моим рассудждениям, которые, по вашему же предложению, сделаны

ближе к теории.

(а именно, в C++ есть указатели на иммутабельные объекты, иммутабельные указатели на объекты, иммутабельные указатели на иммутабельные объекты, ссылки на иммутабельные объекты, но нет иммутабельных ссылок)

Вы хотите сказать, что ссылки в C++ мутабельны?

Тогда вот вам:

int i  = 0;
int & ri = i;
int j = 0;

покажите как сделать ri ссылкой на j.

Сама концепция уничтожения объекта в практическом отношении востребована в языке C++ только из-за особенностей используемого архаичного механизма ручного управления памятью.

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

Ну да, касательно разработчиков Rust остается только сказать "а мужики-то и не знают"... facepalm.jpg

покажите как сделать ri ссылкой на j.

#include <stdio.h>

int & f () {
  int i = 1;
  return i;
}

int & g () {
  int j = 2;
  return j;
}

int main () {
  int & ri = f();
  g ();
  printf ("ri = %X\n", ri);
  return 0;
}

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

В ряде предметных областей вообще не используют управление памятью.

Ну да, касательно разработчиков Rust остается только сказать "а мужики-то и не знают"... 

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

Да блин, ну ёжкинижеж... Не, ну как так-то?

У вас же там UB.
https://wandbox.org/permlink/IazzAnT1hLsEcGP2

Не говоря уже про то, что задача была вообще другой.

Завязывали бы вы с рассуждениями про C++, а?

Ну ведь степень вашей невменяемости уже даже не удивление, я прям таки отторопь вызывает.

У вас же там UB.

Конечно UB. Но вы спросили, как сделать – я вам показал.

https://wandbox.org/permlink/IazzAnT1hLsEcGP2

А вы параноидальный контроль-то свой выключите. Вы свои проекты тоже с такими ключами компилите?

Не говоря уже про то, что задача была вообще другой.

Я не знаю, какая была задача у вас, но я вам показал именно то, что хотел – что неизменность ссылок в C++ не гарантируется (в отличие от некоторых других языков). Если чуть посложнее код написать, то можно и без варнингов скомпилироваться и исполниться. Это уж я вам оставлю в качестве упражнения.

Конечно UB. Но вы спросили, как сделать – я вам показал.

Дяденька, а вы вменяемы вообще? Показали как выстрелить в ногу и считаете это нормальным? Ну ахринеть.

Вы свои проекты тоже с такими ключами компилите?

Случается.

Я не знаю, какая была задача у вас

Так я повторю:

int i  = 0;
int & ri = i;
int j = 0;

В одной области видимости есть живые i и j. Нужно сделать так, чтобы ссылка ri, которая ссылалась на i, начала ссылаться на j. Валидным и легальным способом, само собой.

но я вам показал именно то, что хотел – что неизменность ссылок в C++ не гарантируется

Вы показали (в очередной раз) свое незнание предмета разговора.

Конечно, видно, что вы спорите чисто ради спора, но тем не менее я всё же призываю вас немножко задуматься. Вам не всё ли будет равно, упадёт ваша программа “валидным и легальным способом” или не очень валидным и даже совсем нелегальным? Так-то и иммутабельность низачем не нужна, если сразу правильно программы писать. Вы же валидно и легально не можете присвоить иммутабельной переменной новое значение, тогда зачем писать слово const?

Конечно, видно, что вы спорите чисто ради спора

Да я вообще не спорю, а показываю несостоятельность некоторых ваших заявлений. Чтобы было лучше понятно, что в C++ вы разбираетесь как "свинья в апельсинах", пардон май френч.

Вам не всё ли будет равно, упадёт ваша программа “валидным и легальным способом” или не очень валидным и даже совсем нелегальным?

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

Так-то и иммутабельность низачем не нужна, если сразу правильно программы писать.

Не уводите разговор в сторону. Вы полезли судить о C++, вас в очередной раз макнули в то, что вы неправы в своих утверждениях про C++. И сама по себе иммутабельность здесь не причем. Причем ваша псевдо ученость.

Это не

Использование трюков с UB 

, а демонстрация того, что в C++ нет никакого способа обеспечения иммутабельности ссылок. Именно с чем вы взялись спорить, а теперь почему-то назыаете это уводом разговора в сторону. А никакого другого смысла, кроме защиты от ошибок, механизм иммутабельности не несёт вообще.

а демонстрация того, что в C++ нет никакого способа обеспечения иммутабельности ссылок

Ссылку в C++ вы просто так изменить не сможете. Отсюда и задачка с i, j и ri. Т.е. назвать ссылки в C++ мутабельными -- это от глубокого знания предмета, не иначе.

И речь про ссылки. Не про объекты, ссылки на которые взяты. Именно про ссылки.

У вас может быть ссылка на const- объект. Или ссылка на не-const объект. Но вот поменять значение самой ссылки вы просто так не сможете.

А никакого другого смысла, кроме защиты от ошибок, механизм иммутабельности не несёт вообще.

Мощно, внушаить. Еще что-то такого же масштаба задвинуть сможете?

У вас может быть ссылка на const- объект. Или ссылка на не-const объект. Но вот поменять значение самой ссылки вы просто так не сможете.

Я вам только что привёл пример простейшего кода, в котором ссылка ri сначала ссылается на переменную i, потом на переменную j, а потом вообще в никуда. И этот код является корректным с точки зрения синтаксиса C++ и компилируется при нормальных условиях в исполняемый модуль, хотя его результат работы и не определён (ub).

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

Я вам только что привёл пример простейшего кода

Вы привели пример невалидного кода.

На этом разговор уже закончен. Для вменяемых программистов. Вы же вольны теоретизировать дальше.

Чего??? Из всех назначений лямбда-выражений, "вложенные функции" - самое основное.

Отвечу тут дабы не путать вложенность.

Всё верно.

C++23 немного улучшает нам ситуацию с введением static lambda где определяется static operator().

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

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

В отличии от обычных функций где стек напишет наше имя функции явно , или у вложенных если бы они были.

Про инлайнинг это отдельная тема если вдруг захочется возразить.

Лямбда-выражения придуманы, чтобы можно было более компатно работать с экземплярами классов, у которых один метод (вспомните "листенеры" java).

Ну вообще лямбда-выражения придуманы задолго до классов и даже до программирования, как такового.

Ага. Сперва придумали лямбды для С++, потом к ним прикрутили всё остальное, что есть в С++.

Или Вы не о С++ хотели сказать? А зачем?

Ну так C++ не в вакууме существует, а, напротив, интегрирует в себя полезные возможности других языков.

Итак, в C++11 тип возвращаемого значения лямбда-выражения нужно было указывать явно, если оно не было очевидным. В C++14 этого уже не требуется благодаря автоматическому выводу типов:

Пример без C++14:

auto lambda = []() -> int { return 42; };

Пример с C++14:

auto lambda = []() { return 42; }; // тип значения int выводится автоматически

Глупость какая-то. В C++11 автовывод типов лямбд прекрасно работал. В C++14 автовывод типов был добавлен, наоборот, для обычных функций.

При этом все примеры лямбд из с++11 без указания типа возвращаемого значения

Также C++17 наконец стали довольно часто юзать stl. Вообще, можно было
юзать и начиная с версии 14, но только в 17 версии это обрело свою
массовость:

Можно это как-то пояснить. Кто начали юзать stl? Лямбды?

Дополните статью про косяки захвата и структурного связывания (lambda capture and structure binding)

А что за косяки структурного связывания?

Итак, в C++11 тип возвращаемого значения лямбда-выражения нужно было указывать явно, если оно не было очевидным

Не понял. О чем тогда может идти речь в "если оно не было очевидным", как не об автоматическом выводе возвращаемого типа? Поясните, что вы имели в виду под "очевидностью" в С++11?

C версии C++20 лямбду можно использовать вместе с шаблонами:

auto genericLambda = []<typename T>(T x) {

Ым... простите, но всякий раз, когда вы используете auto при объявлении типов в списке параметров лямбды, вы уже тем самым объявляете "шаблонную" (generic) лямбду, что стало возможно намного раньше: начиная С++14. Синтаксис с ключевым словом template, введенный в С++20 - это не более чем явный/"классический" вариант точно того же.

Более того, начиная с С++20, этот синтаксис синхронизирован и с обычными функциями, в списках параметров которых тоже можно использовать auto. Использование auto в списке параметров функции (не важно, обычной или лямбды) - это ни что иное, как сокращенный (abbreviated) синтаксис объявления шаблона.

Так что "шаблонность" в лямбдах присутствует с С++14, а не с С++20.

Спасибо за комментарий, намеренно искал, написал ли кто-то уже об этом.

И кмк это ведь как раз одно из важнейших нововведений C++14, оказавшее огромное влияние на написание кода.

Это не более чем развитие идеи auto/decltype, то есть поддержки парадигмы type-agnostic кода. Появилась она в С++11. Парадигма эта существует со времен Царя Гороха, но массивным нововведениям в ее поддержку мы обязаны именно С++11.

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

Хотя в таком случае, я бы предпочёл видеть отдельную функцию.

Все эти лямбды - это стрелочные функции из JavaScript, и как по мне они не упрощают понимание кода, this my opinion

Ваша компания теперь раз в пару лет будет писать про одно и то же?

https://habr.com/ru/companies/otus/articles/673580/

Я уж не говорю о том, что на Хабре в 2019 году были две больших статьи про лямбды в c++:

https://habr.com/ru/companies/otus/articles/444524/

https://habr.com/ru/companies/otus/articles/455978/

Тоже перевод, и тоже от вашей компании.

Sign up to leave a comment.