Лямбды в качестве вложенных функций.
От: Went  
Дата: 08.05.15 09:45
Оценка:
Здрасте. Заметил, что С++11 — ые лямбды очень удобно использовать в качестве вложенных функций. Вот так:
int get_something(const SomeStruct& given)
{
  // Считаем что-то большое, большой контекст локальных переменных.
  // ...

  auto calculate_some = [&](int val)
  {
    // Считаем что-то из val, учитывая контекст внешней функции
    // ...
  };

  // Используем эту вложенную функцию несколько раз
  calculate_some(1);
  calculate_some(2);
  calculate_some(10);

  // Какой-то результат
  return 1;
}


То есть суть тривиальна — полноценная вложенная функция с захватом контекста. Все работает. Однако меня беспокоит наличие потенциальных накладных расходов, связанных с таким подходом:
1. Может ли эта функция заинлайниться?
2. Дорого ли передавать "[&]" в качестве контекста? Просто передается указатель на стек или это может быть дороже?
3. Дорога ли сама иницализация лямбда-функтора? Понятно, что она вызывается единожды за вызов основной функции, но все же.
4. Какие еще могут быть подводные камни?
Re: Лямбды в качестве вложенных функций.
От: jazzer Россия Skype: enerjazzer
Дата: 08.05.15 09:50
Оценка: 2 (1) +2
Здравствуйте, Went, Вы писали:

W>Здрасте. Заметил, что С++11 — ые лямбды очень удобно использовать в качестве вложенных функций.


Угу, и еще много как их можно использовать:
http://rsdn.ru/forum/cpp/5816278.1
Автор: jazzer
Дата: 13.10.14

http://rsdn.ru/forum/philosophy/5949645.1
Автор: jazzer
Дата: 10.02.15


W>1. Может ли эта функция заинлайниться?


всё инлайнится и ничего не стоит (по крайней мере, на тех примерах, что я гонял на GCC4.9), пользуй смело.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Лямбды в качестве вложенных функций.
От: Went  
Дата: 08.05.15 10:45
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Угу, и еще много как их можно использовать:

J>http://rsdn.ru/forum/cpp/5816278.1
Автор: jazzer
Дата: 13.10.14

Ну, это я еще кое-как понял, правда С++14 это еще для меня только мечты

J>http://rsdn.ru/forum/philosophy/5949645.1
Автор: jazzer
Дата: 10.02.15

А тут у меня уже совсем моск потек

J>всё инлайнится и ничего не стоит (по крайней мере, на тех примерах, что я гонял на GCC4.9), пользуй смело.

Пасиба
Re: Лямбды в качестве вложенных функций.
От: VTT http://vtt.to
Дата: 08.05.15 14:12
Оценка:
Здравствуйте, Went, Вы писали:

W>1. Может ли эта функция заинлайниться?

Может, а может и не заинлайниваться.

W>2. Дорого ли передавать "[&]" в качестве контекста? Просто передается указатель на стек или это может быть дороже?

W>3. Дорога ли сама иницализация лямбда-функтора? Понятно, что она вызывается единожды за вызов основной функции, но все же.
Мне кажется, что лучше думать о лямбде, как об анонимном объекте, полями которого являются используемые сущности из захваченного контекста.
То, что этот объект будет создаваться в единственном месте, дает компилятору дополнительное поле деятельности для оптимизации.
Если этот объект используется на месте (а не передаваться куда-то дальше, например в std::function), то в оптимизированном коде,
скорее всего, не будет создаваться вообще никаких дополнительных объектов или оверхед будет сравним с ручной реализацией.

W>4. Какие еще могут быть подводные камни?

Некоторые рекомендуют указывать все захватываемые аргументы в явном виде вместо [&].
Для облегчения работы компилятору и большей определенности.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[2]: Лямбды в качестве вложенных функций.
От: Went  
Дата: 08.05.15 15:03
Оценка:
Здравствуйте, VTT, Вы писали:

VTT>Может, а может и не заинлайниваться.

Ну, понятно, это зависит целиком от компилятора. Вопрос в принципиальной возможности.

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

Да, несомненно это объект, и именно так мы о нем и думаем.

VTT>Некоторые рекомендуют указывать все захватываемые аргументы в явном виде вместо [&].

А вот почему? Я сам так делал, а потом подумал — а зачем?

VTT>Для облегчения работы компилятору и большей определенности.

Вот тут самое интересное. Теоретически, указывая [&] я говорю компилятору: "захвати Stack Pointer и будь счастлив, все переменные можно будет вывести из смещений относительно этого адреса". Я понимаю, когда речь идет о [=], тут, понятно, объект-функция должен будет хранить все по значению. Или обычный компилятор тупо тянет в лямбду все внешние ссылки, которые он встретил при компиляции ее тела?
Re[3]: Лямбды в качестве вложенных функций.
От: VTT http://vtt.to
Дата: 08.05.15 15:40
Оценка:
Здравствуйте, Went, Вы писали:

VTT>>Некоторые рекомендуют указывать все захватываемые аргументы в явном виде вместо [&].

W>А вот почему? Я сам так делал, а потом подумал — а зачем?
Но вот, например, захват какой-то переменной по ссылке прямо говорит, что эта переменная должна пережить эту лямбду. Если захватывать все сразу [&], то чтобы это понять, придется рассматривать тело функции.
Или можно случайно захватить или изменить что-то лишнее. Допустим, в контексте есть некоторый x, а в теле mutable лямбды должна быть локальная переменная x, а ее забывают объявить как переменную, или вообще забывают посчитать.
При рефакторинге (и вообще копипасте) можно легко напортачить.

W>Вот тут самое интересное. Теоретически, указывая [&] я говорю компилятору: "захвати Stack Pointer и будь счастлив, все переменные можно будет вывести из смещений относительно этого адреса". Я понимаю, когда речь идет о [=], тут, понятно, объект-функция должен будет хранить все по значению. Или обычный компилятор тупо тянет в лямбду все внешние ссылки, которые он встретил при компиляции ее тела?

Что-то я не думаю, что там вообще Stack Pointer в лямбду сохраняется. Тем более, что многие захватываемые переменные на самом деле будут существовать только в регистрах (к чему компилятор и стремится). Указание [&] скорее звучит как "Посмотри, что я там использую в теле лямбды из внешнего контекста и, при необходимости, сохрани это все на время жизни этой лямбды где-нибудь." Когда лямбда утилизируется на месте, то такой необходимости может и не появиться, или придется сохранять только кое-что. И не обязательно даже реально по ссылке.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[3]: Лямбды в качестве вложенных функций.
От: niXman Ниоткуда https://github.com/niXman
Дата: 08.05.15 15:57
Оценка: +1 -1
Здравствуйте, Went, Вы писали:

W>Да, несомненно это объект, и именно так мы о нем и думаем.


ну, не всегда...
если нет списка захвата — то просто локальная функция:
void(*fp0)() = [](){};
void(*fp1)(int) = [](int v){};

fp0();
fp1(3);
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[4]: Лямбды в качестве вложенных функций.
От: Went  
Дата: 08.05.15 17:30
Оценка:
Здравствуйте, VTT, Вы писали:

VTT>При рефакторинге (и вообще копипасте) можно легко напортачить.

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

VTT>Что-то я не думаю, что там вообще Stack Pointer в лямбду сохраняется. Тем более, что многие захватываемые переменные на самом деле будут существовать только в регистрах (к чему компилятор и стремится). Указание [&] скорее звучит как "Посмотри, что я там использую в теле лямбды из внешнего контекста и, при необходимости, сохрани это все на время жизни этой лямбды где-нибудь." Когда лямбда утилизируется на месте, то такой необходимости может и не появиться, или придется сохранять только кое-что. И не обязательно даже реально по ссылке.

Сейчас посмотрел что делает вижуаловский компилятор с "лямбдами — локальными функциями". Все достаточно оптимально. Использует общий esp, подразумевая, что функция вызывается только из текущего места.
Re[4]: Лямбды в качестве вложенных функций.
От: Abyx Россия  
Дата: 08.05.15 21:27
Оценка: +1
Здравствуйте, niXman, Вы писали:

W>>Да, несомненно это объект, и именно так мы о нем и думаем.


X>ну, не всегда...

X>если нет списка захвата — то просто локальная функция:
X>
X>void(*fp0)() = [](){};
X>void(*fp1)(int) = [](int v){};

X>fp0();
X>fp1(3);
X>


вообще-то объект. с оператором приведения к указателю на функцию.
In Zen We Trust
Re[5]: Лямбды в качестве вложенных функций.
От: niXman Ниоткуда https://github.com/niXman
Дата: 08.05.15 22:37
Оценка:
Здравствуйте, Abyx, Вы писали:

A>вообще-то объект. с оператором приведения к указателю на функцию.

как вот это:
struct lambda {
   void operator()() {}
};

можно привести к этому:
void(*fp)();

?

хочу пример.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[6]: Лямбды в качестве вложенных функций.
От: Abyx Россия  
Дата: 08.05.15 23:00
Оценка:
Здравствуйте, niXman, Вы писали:

A>>вообще-то объект. с оператором приведения к указателю на функцию.

X>как вот это:
X>
X>struct lambda {
X>   void operator()() {}
X>};
X>

X>можно привести к этому:
X>
X>void(*fp)();
X>

X>?

X>хочу пример.


например копипастой.

struct lambda {
  static void impl() { f(); g(); }
  void operator()() { f(); g(); }
  decltype(&lambda::impl) operator() { return impl; }
};


но вообще стандарт не говорит как именно это должно происходить. однако тип лямбда-выражения это всегда класс.
In Zen We Trust
Re[7]: Лямбды в качестве вложенных функций.
От: niXman Ниоткуда https://github.com/niXman
Дата: 08.05.15 23:09
Оценка:
Здравствуйте, Abyx, Вы писали:

A>
A>struct lambda {
A>  static void impl() { f(); g(); }
A>  void operator()() { f(); g(); }
A>  decltype(&lambda::impl) operator() { return impl; }
A>};
A>


A>но вообще стандарт не говорит как именно это должно происходить. однако тип лямбда-выражения это всегда класс.

ну это же мошейничество со стороны компилятора...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[6]: Лямбды в качестве вложенных функций.
От: Evgeny.Panasyuk Россия  
Дата: 08.05.15 23:25
Оценка: +1
Здравствуйте, niXman, Вы писали:

A>>вообще-то объект. с оператором приведения к указателю на функцию.

X>как вот это:
X>
X>struct lambda {
X>   void operator()() {}
X>};
X>

X>можно привести к этому:
X>
X>void(*fp)();
X>

X>?
X>хочу пример.

Например так:
struct lambda
{
   void operator()() {}
   
   static void lambda_call_operator_invoker() // see ISO
   {
       return lambda{}();
   }

   using F = void (*)();
   operator F() const
   {
       return lambda_call_operator_invoker;
   }
};

int main()
{
    void(*fp)() = lambda{};
}

Но даже если бы такого способа не было — это всё равно ничего не поменяло бы. Стандарт говорит что тип выражения лямбды — "unnamed nonunion class type", и при определённых обстоятельствах для этого типа доступно преобразование в указатель на функцию.
А уже как оно там реализовано внутри — дело десятое — например компилятор может не создавать оператор преобразования, а дёргать какие-нибудь внутренние интринсинки при необходимости
Re[4]: Лямбды в качестве вложенных функций.
От: Evgeny.Panasyuk Россия  
Дата: 08.05.15 23:35
Оценка: +1
Здравствуйте, niXman, Вы писали:

W>>Да, несомненно это объект, и именно так мы о нем и думаем.

X>ну, не всегда...
X>если нет списка захвата — то просто локальная функция:
X>
X>void(*fp0)() = [](){};
X>void(*fp1)(int) = [](int v){};
X>fp0();
X>fp1(3);
X>


LIVE DEMO
#include <type_traits>

int main()
{
    using namespace std;

    auto x = [](int v){};
    void (*f)(int) = x;
    static_assert(is_class<decltype(x)>::value, "");
    static_assert(is_class<decltype(f)>::value, "");
}
////////////////////////////////////////////////////////
clang++ -std=c++1y -O3 -Wall main.cpp && ./a.out
main.cpp:10:5: error: static_assert failed ""
    static_assert(is_class<decltype(f)>::value, "");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Re[8]: Лямбды в качестве вложенных функций.
От: Abyx Россия  
Дата: 09.05.15 13:46
Оценка:
Здравствуйте, niXman, Вы писали:

A>>
A>>struct lambda {
A>>  static void impl() { f(); g(); }
A>>  void operator()() { f(); g(); }
A>>  decltype(&lambda::impl) operator() { return impl; }
A>>};
A>>


A>>но вообще стандарт не говорит как именно это должно происходить. однако тип лямбда-выражения это всегда класс.

X>ну это же мошейничество со стороны компилятора...

это же код который генерируется, причем он даже не виден программисту.
нет никаких причин по которым он должен быть чистым, в т.ч. соблюдать DRY
In Zen We Trust
Re[5]: Лямбды в качестве вложенных функций.
От: Ops Россия  
Дата: 11.05.15 03:02
Оценка:
Здравствуйте, Went, Вы писали:

W>Сейчас посмотрел что делает вижуаловский компилятор с "лямбдами — локальными функциями". Все достаточно оптимально. Использует общий esp, подразумевая, что функция вызывается только из текущего места.


Так с этого и надо было начинать. Есть сомнения — смотри, что выдает компилятор. Да и то, пока тебе профайлер это место не показал — можешь забить, пусть оно 100 раз неэффективно, но общих тормозов не дает.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[6]: Лямбды в качестве вложенных функций.
От: Went  
Дата: 11.05.15 10:55
Оценка:
Здравствуйте, Ops, Вы писали:

Ops>Здравствуйте, Went, Вы писали:


W>>Сейчас посмотрел что делает вижуаловский компилятор с "лямбдами — локальными функциями". Все достаточно оптимально. Использует общий esp, подразумевая, что функция вызывается только из текущего места.


Ops>Так с этого и надо было начинать. Есть сомнения — смотри, что выдает компилятор. Да и то, пока тебе профайлер это место не показал — можешь забить, пусть оно 100 раз неэффективно, но общих тормозов не дает.

Кстати, и даже инлайнит. Причем, если вызов идет из цикла — то инлайнит, если дважды вызов руками — делает call.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.