Функція, що повертає лямбда-вираз


88

Цікаво, чи можливо написати функцію, яка повертає лямбда-функцію в C ++ 11. Звичайно, одна проблема полягає в тому, як оголосити таку функцію. Кожна лямбда має тип, але цей тип не виражається в C ++. Я не думаю, що це буде працювати:

auto retFun() -> decltype ([](int x) -> int)
{
    return [](int x) { return x; }
}

Ані це:

int(int) retFun();

Мені не відомі будь-які автоматичні перетворення з лямбд у, скажімо, вказівники на функції, або деякі подібні. Чи єдиним рішенням є ручна робота об’єкта функції та її повернення?


1
Щоб додати те, що вже було сказано, лямбда-функції без стану можуть конвертуватись у покажчики на функції.
snk_kid

3
ІМО ваш перший параметр не буде працювати, оскільки лямбда в decltypeне є такою ж, як у тілі функції, і тому має інший тип (навіть якщо ви включили оператор return)
Мотті

1
До речі, якщо лямбда має порожнє речення захоплення, воно може неявно конвертуватися у покажчик для функціонування.
GManNickG

@GMan: Якщо ви не використовуєте Visual C ++ 2010 або версію g ++, випущену більше року тому (або недалеко від неї). Неявне перетворення ламбда-вказівника на покажчик функції було додано лише в березні 2010 року в N3092.
James McNellis

4
Лямбда-вирази загалом не можуть відображатися в неоцінених операндах. Так decltype([](){})або sizeof([]() {})неправильно сформований, де б ви його не писали.
Йоханнес Шауб - літ

Відповіді:


98

Вам не потрібен ручний об’єкт функції, просто використовуйте std::function, в який лямбда-функції можна конвертувати:

Цей приклад повертає цілочисельну функцію ідентичності:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}

6
Дух! Я люблю StackOverflow. Мені знадобилось би набагато більше часу, щоб дослідити тему, а потім отримати відповідь на StackOverflow. Дякую Шон!
Bartosz Milewski

6
Це призведе до виділення пам'яті, хоча в конструкторі std::function.
Максим Єгорушкін

2
@ Максим Єгорушкін std :: функція має семантику переміщення, плюс вона може використовувати власні розподільники, а в робочому проекті C ++ 0x є такі примітки: "[Примітка: впровадження рекомендується уникати використання динамічно виділеної пам'яті для малих об'єктів, що викликаються, наприклад , де ціль f - це об’єкт, що містить лише вказівник або посилання на об’єкт та покажчик функції-члена. —завершення примітки] ", тому в основному ви не можете робити багато припущень щодо того, яку стратегію розподілу використовує конкретна реалізація, але ви повинні мати можливість щоб у будь-якому випадку використовувати власні (об’єднані) розподільники.
snk_kid

1
@Maxim: Я відповів на запитання "Чи єдиним рішенням є ручна робота об’єкта функції та її повернення?"
Шон

2
Тільки майте на увазі, що std::functionвикористовується стирання типу, що може означати вартість здійснення виклику віртуальної функції під час виклику std::function. Щось, про що слід знати, якщо функція, що повертається, буде використана в тісному внутрішньому циклі або в іншому контексті, де важлива незначна неефективність.
Ентоні Холл,

29

Для цього простого прикладу вам не потрібно std::function.

Зі стандартного п. 5.1.2 / 6:

Тип закриття для лямбда-виразу без лямбда-захоплення має загальнодоступну невіртуальну неявну функцію перетворення const на вказівник на функцію, що має ті самі параметри та типи повернення, що і оператор виклику функції типу закриття. Значення, яке повертає ця функція перетворення, має бути адресою функції, яка при виклику має такий самий ефект, як виклик оператора виклику функції типу закриття.

Оскільки у вашої функції немає захоплення, це означає, що лямбда може бути перетворена в покажчик на функцію типу int (*)(int):

typedef int (*identity_t)(int); // works with gcc
identity_t retFun() { 
  return [](int x) { return x; };
}

Це моє розуміння, виправте мене, якщо я помиляюся.


1
Це звучить правильно. На жаль, це не працює з поточним компілятором, який я використовую: VS 2010. std :: перетворення функції, здається, працює.
Bartosz Milewski

3
Так, остаточне формулювання цього правила надто пізно надійшло для VC2010.
Ben Voigt

Я додав приклад коду. Ось повна програма .
jfs

@JFSebastian - Яке життя лямбди в цьому прикладі? Чи достатньо довго пережити результат перетворення на покажчик функції?
Flexo

1
@JFSebastian - Я, здається, не можу знайти відповіді на це, тому я поставив це як власне запитання: stackoverflow.com/questions/8026170/…
Flexo

21

Ви можете повернути лямбда-функцію з іншої лямбда-функції, оскільки ви не повинні чітко вказувати тип повернення лямбда-функції. Просто напишіть щось подібне у глобальному масштабі:

 auto retFun = []() {
     return [](int x) {return x;};
 };

2
Це справедливо лише тоді, коли зовнішня лямбда складається лише з оператора return. В іншому випадку вам доведеться вказати тип повернення.
Bartosz Milewski,

3
Це найкраща відповідь, оскільки вона не вимагає поліморфізму виконання std :: function і дозволяє лямбда мати непорожній список захоплення, однак я б використовував const auto fun = ...
robson3.14

2
@BartoszMilewski не відповідає дійсності, оскільки C ++ 14.
джоєв

19

Хоча питання конкретно задає C ++ 11, заради інших, хто натрапляє на це і має доступ до компілятора C ++ 14, C ++ 14 тепер дозволяє виводити типи повернень для звичайних функцій. Тож приклад у питанні можна налаштувати просто так, щоб він працював за бажанням, просто опустивши -> decltypeречення ... після списку параметрів функції:

auto retFun()
{
    return [](int x) { return x; }
}

Однак зауважте, що це не спрацює, якщо return <lambda>;у функції з’являється більше одного . Це пов’язано з тим, що обмеження на відрахування типу повернення полягає в тому, що всі оператори return повинні повертати вирази одного типу, але кожен лямбда-об’єкт отримує компілятор свій власний унікальний тип, тому return <lambda>;вирази матимуть різний тип.


4
Навіщо згадувати виведені типи c ++ 14, але опускати поліморфні лямбди? auto retFun() { return [](auto const& x) { return x; }; }
sehe

1

Ви повинні писати так:

auto returnFunction = [](int x){
    return [&x](){
        return x;
    }();
};

щоб отримати повернення як функцію та використовувати її як:

int val = returnFunction(someNumber);

0

Якщо у вас немає c ++ 11 і ви використовуєте ваш код c ++ на мікроконтролерах, наприклад. Ви можете повернути порожній покажчик, а потім виконати приведення.

void* functionThatReturnsLambda()
{
    void(*someMethod)();

    // your lambda
    someMethod = []() {

        // code of lambda

    };

    return someMethod;
}


int main(int argc, char* argv[])
{

    void* myLambdaRaw = functionThatReturnsLambda();

    // cast it
    auto myLambda = (void(*)())myLambdaRaw;

    // execute lambda
    myLambda();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.