TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #729 4.7K
Лямбды без захвата
#опытным

Сегодня немного проясню ситуацию с лямбдами без захвата.

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

Давайте взглянем на простую лямбду:

auto fun = [](int i) { return i*2;};


У нее очевидно нет захвата, поэтому она как раз наш абонент. С помощью cppinsights можно посмотреть под капот компилятора и то, во что лямбды и другие сущности превращаются. Примерно вот такую сущность сгенерируют компилятор для этой лямбды:

class __lambda_11_15
{
public:
inline int operator()(int i) const
{
return i * 2;
}

using retType_11_15 = int (*)(int);
inline constexpr operator retType_11_15 () const noexcept
{
return __invoke;
}

private:
static inline /*constexpr */ int __invoke(int i)
{
return __lambda_11_15{}.operator()(i);
}
};


И тут много интересного!

Например, у лямбды без захвата все же генерируется оператор вызова operator().

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

То есть вот примерно как это работает:

int apply_function(int (*func)(int), int value) {
return func(value); // Вызываем переданную функцию
}

int main() {
auto fun = [](int i) { return i2;};
fun(42);
apply_function(fun, 42);
return 0;
}


Здесь у нас 2 вида использования лямбды: через объект замыкания и через коллбэк в apply_function. Посмотрим, что будет вызываться в каждом конкретном случае:

int main()
{
__lambda_11_15 fun = __lambda_11_15{};
fun.operator()(42);
apply_function(fun.operator __lambda_11_15::retType_11_15(), 42);
return 0;
}


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

Зачем два способа вызова? Почему нельзя обойтись просто приведением к указателю на функцию?

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

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

Надеюсь, теперь вы чуть больше о лямбдах знаете)

Know more. Stay cool.

#cppcore #cpp11
  • 🔥 19
  • 👍 12
  • ❤ 5
  • ❤‍🔥 1
More from @grokaemcpp
  1. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  2. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  3. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  4. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  5. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  6. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →