Общие лямбда-выражения: синтаксический сахар или нет?

Приносят ли универсальные лямбда-выражения С++ 14 реальное улучшение языка или они являются своего рода синтаксическим сахаром? Бывают ли ситуации, когда

[](auto param1, auto param2, /* ... */ auto paramN)
{
    return /* ... */;
}

нельзя заменить на

template <typename Type1, typename Type2, /* ... */ typename TypeN>
auto foo(Type1&& param1, Type2&& param2, /* ... */ TypeN&& paramN)
{
    return  /* ... */;
}

or

struct bar
{
    template <typename Type1, typename Type2, /* ... */ typename TypeN>
    auto operator()(Type1&& param1, Type2&& param2, /* ... */ TypeN&& paramN)
    {
        return  /* ... */;
    }
};

?


@Kerrek SB предоставил очень интересные ссылки в комментариях, иллюстрирующих возможности общих лямбда-выражений:


person Constructor    schedule 19.08.2014    source источник
comment
См. github.com/ldionne/hana (также stackoverflow.com/questions/25338795/)   -  person Kerrek SB    schedule 19.08.2014
comment
Вы можете спросить то же самое о неуниверсальных лямбдах.   -  person Simple    schedule 19.08.2014
comment
изначально lambda — это синтаксический сахар; Для более легкого ›o‹   -  person ikh    schedule 19.08.2014
comment
@KerrekSB Спасибо за ссылку.   -  person Constructor    schedule 19.08.2014
comment
Ваш вопрос предполагает, что синтаксический сахар никогда не приносит реального улучшения языка. Я категорически не согласен с этим.   -  person    schedule 19.08.2014
comment
@Simple Думаю, я знаю ответ для неуниверсальных лямбда-выражений. Они точно синтаксический сахар, не так ли?   -  person Constructor    schedule 19.08.2014
comment
@hvd Хорошо, я согласен с вами, я использовал неправильные слова в своем вопросе. А как же вторая часть вопроса?   -  person Constructor    schedule 19.08.2014
comment
@Constructor Стандарт уже требует, чтобы компилятор заменил лямбду (универсальную или нет) автоматически сгенерированным классом с функцией-членом operator(). Если компилятор может это сделать, я не могу представить ситуацию, в которой вы не могли бы сделать это самостоятельно. (Но в недавнем прошлом я видел несколько очень творческих применений лямбда-выражений, которые позволяют делать то, что я не считал возможным, поэтому я еще не совсем уверен.)   -  person    schedule 19.08.2014
comment
Помните, что лямбда-выражения также могут захватывать локальные переменные; поэтому в общем случае эквивалентному написанному от руки классу также потребуются элементы данных и конструктор для их инициализации. Поскольку использование лямбда позволяет избежать подверженного ошибкам дублирования объявления и инициализации членов, я бы сказал, что это больше, чем просто сахар.   -  person Mike Seymour    schedule 19.08.2014
comment
@hvd Но общая лямбда должна быть заменена шаблоном класса или классом с шаблонным членом, не так ли? Таким образом, есть разница в поведении компилятора, когда он имеет дело с простой лямбдой и общей...   -  person Constructor    schedule 19.08.2014
comment
@MikeSeymour Я полностью согласен с тем, что лямбды - это больше, чем простой синтаксический сахар, я неправильно задал свой вопрос. На самом деле меня очень интересует вторая часть.   -  person Constructor    schedule 19.08.2014


Ответы (3)


Для необобщенных лямбда-выражений C++11 можно выполнить довольно простой перевод:

void foo()
{
    int i = 42; int j = 22;
    auto f = [i, &j](int k) { return i + j + k };
    // proceed to use f
 }

Например:

void foo()
{
    int i = 42; int j = 22;
    struct {
        int i; int& j;
        // can't deduce return type
        int operator()(int k) const
        { return i + j + k; }
    } f { i, j };
    // proceed to use f
}

Для общих лямбда-выражений C++14 все не так просто. Предположим, на этот раз мы используем auto f = [i, &j](auto k) { return i + j + k; }. Затем мы должны создать следующий оператор вызова:

template<typename T>
auto operator()(T k) const { return i + j + k; }

Проблема в том, что мы не можем определить шаблон в области действия функции (ограничение, также известное как отсутствие локальных шаблонов). Таким образом, мы должны переместить определение типа замыкания из объемлющей функции в область пространства имен (присвоив ему имя в процессе), а затем использовать closure_type f { i, j };. Между прочим, это означает, что мы должны дать классу и его оператору некоторую форму связи, тогда как локальные определения функций не имеют связи.

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

person Luc Danton    schedule 19.08.2014
comment
Большое спасибо! Как вы думаете, нет ли других различий между общей лямбдой и классом замыкания с шаблоном operator()? - person Constructor; 19.08.2014
comment
@Constructor Тип замыкания (созданный из лямбда-выражения) должен вести себя как такой класс. Фактически это означает, что такие вещи, как auto f = [] {}; auto ptm = &decltype(f)::operator();, должны работать — в этом отношении ничего не изменилось. - person Luc Danton; 19.08.2014
comment
Хм, это работает даже для общей лямбды. Но как? - person Constructor; 19.08.2014
comment
О, не работает, конечно (я имел в виду свой код). coliru сегодня работает немного странно. Да, я знаю об этом трюке, спасибо. - person Constructor; 19.08.2014

О лямбдах в целом:

Некоторые считают это «действительно изящным!»; другие видят в этом способ написания опасно непонятного кода. Имхо, оба правы. --- Бьерн Страуструп

Я думаю, это вопрос того, как вы используете лямбду. В качестве небольшой локальной функции закрытия, которую вы используете для улучшения обработки функций, которые принимают объекты функций в качестве параметров (например, std::sort), я на самом деле не видел примера, где общие лямбда-выражения добавят какое-либо преимущество.

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

Поясню немного:

void foo(std::vector<int>& v)
{
    std::sort(v.begin(), v.end(), [](int i, int j) { return (i < j); });
}

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

void C::foo()
{
    registerLazyMultithreadedCallback([=this]() { return this; });
}

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

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

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

person Stefan Weiser    schedule 19.08.2014
comment
Ваш ответ касается простых лямбд, а не общих. - person Constructor; 19.08.2014
comment
Речь идет об использовании лямбд. Если вы используете его локально для вспомогательных функций, нет необходимости в универсальном программировании, потому что типы понятны... - person Stefan Weiser; 19.08.2014
comment
Кроме того, то, на что вы жалуетесь, в равной степени применимо к функциям или функторам, закодированным вручную, поэтому лямбды ни в чем не виноваты. В ситуациях, когда они используются правильно, они работают правильно (т.е. одинаково) и выглядят гораздо более читаемыми. Вы можете написать опасный малопонятный код без лямбда-выражений, и тогда он займет в два раза больше строк. - person underscore_d; 11.05.2017

Приносят ли универсальные лямбда-выражения С++ 14 реальное улучшение языка?

Да.

или они своего рода синтаксический сахар?

Да.

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

person tenfour    schedule 19.08.2014
comment
Нет нет. Я согласен, что они являются реальным улучшением. Я использовал неправильные слова. А как же вторая часть вопроса? - person Constructor; 19.08.2014
comment
Проблемы создает не синтаксический сахар, а разработчик, который не может правильно обрабатывать такие вещи, как время жизни объекта и области видимости. Лямбда в целом немного маскирует эти темы из-за захвата. - person Stefan Weiser; 19.08.2014