Вопрос по c++11, c++, functional-programming, lambda – почему лямбда-функции в с ++ 11 не имеют функций <>?

17

Я играю с функциональными возможностями C ++ 11. Единственное, что я нахожу странным, это то, что тип лямбда-функции на самом деле НЕ является функцией & lt; & gt; тип. Более того, лямбда, кажется, не очень хорошо работает с механизмом определения типа.

В приложении приведен небольшой пример, в котором я протестировал переключение двух аргументов функции для добавления двух целых чисел. (Компилятор, который я использовал, был gcc 4.6.2 под MinGW.) В примере тип дляaddInt_f был явно определен с использованием функции & lt; & gt; в то время какaddInt_l лямбда, тип которого определяется типомauto.

Когда я скомпилировал код,flip Функция может принимать явно определенную типовую версию addInt, но не лямбда-версию, выдавая ошибку о том, что testCppBind.cpp:15:27: error: no matching function for call to 'flip(<lambda(int, int)>&)'

Следующие несколько строк показывают, что лямбда-версия (а также «необработанная» версия) может быть принята, если она явно приведена к соответствующей функции & lt; & gt; тип.

Итак, мои вопросы:

Why is it that a lambda function does not have a function<> type in the first place? In the small example, why does not addInt_l have function<int (int,int)> as the type instead of having a different, lambda type? From the perspective of functional programming, what's the difference between a function/functional object and a lambda?

If there is a fundamental reason that these two have to be different. I heard that lambda's can be converted to function<> but they are different. Is this a design issue/defect of C++11, an implementation issue or is there a benefit in distinguishing the two as the way it is? It seems that the type-signature of addInt_l alone has provided enough information about the parameter and return types of the function.

Is there a way to write the lambda so that the above mentioned explicit type-casting can be avoided?

Заранее спасибо.

    //-- testCppBind.cpp --
    #include <functional>
    using namespace std;
    using namespace std::placeholders;

    template <typename T1,typename T2, typename T3>
    function<T3 (T2, T1)> flip(function<T3 (T1, T2)> f) { return bind(f,_2,_1);}

    function<int (int,int)> addInt_f = [](int a,int b) -> int { return a + b;};
    auto addInt_l = [](int a,int b) -> int { return a + b;};

    int addInt0(int a, int b) { return a+b;}

    int main() {
      auto ff = flip(addInt_f);   //ok
      auto ff1 = flip(addInt_l);  //not ok
      auto ff2 = flip((function<int (int,int)>)addInt_l); //ok
      auto ff3 = flip((function<int (int,int)>)addInt0);  //ok

      return 0;
    }

Лямбды преобразуются в анонимные Функторы (или Функции, если они не захватывают окружение). Преобразование их в std :: function приведет к сильной связи между языком и библиотекой и, следовательно, будет очень плохой идеей. MFH
@MFH & lt; nitpick & gt; Лямбды всегда являются объектами анонимных функций. Эти функциональные объекты могут быть преобразованы в указатели на функции. R. Martinho Fernandes
@TingL Это где вы не правы. Ламды - это НЕ библиотечные конструкции, а новая языковая функция. MFH
Вы не должны приниматьstd::function аргументы, главным образом потому, что это запрещает дедукцию типа (что является вашей проблемой здесь). R. Martinho Fernandes

Ваш Ответ

2   ответа
41

std::function это инструмент, полезный дляstore любой вид вызываемого объекта независимо от его типа. Для того, чтобы сделать это, он должен использовать некоторую технику стирания типа, и это требует некоторых накладных расходов.

Любой вызываемый объект может быть неявно преобразован вstd::functionи именно поэтому он обычно работает без проблем.

Я повторю, чтобы было ясно:std::function это не просто лямбды или указатели на функции: это дляany вид вызываемого. Это включает в себя такие вещи, какstruct some_callable { void operator()() {} };, например. Это просто, но это может быть что-то вроде этого:

struct some_polymorphic_callable {
    template <typename T>
    void operator()(T);
};

Лямбда - это просто еще один вызываемый объект, похожий на примерыsome_callable объект выше. Это может быть сохранено вstd::function потому что он может вызываться, но у него нет издержек на стирание типаstd::function.

И комитет планирует сделать лямбды полиморфными в будущем, то есть лямбды, которые выглядят какsome_polymorphic_callable выше. Которыйstd::function типа будет такая лямбда быть?

Теперь ... Вывод параметров шаблона или неявные преобразования. Выбери один. Это правило шаблонов C ++.

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

Решение? Не ограничивайте своих абонентовstd::function, приниматьany kind of callable.

template <typename Fun>
auto flip(Fun&& f) -> decltype(std::bind(std::forward<Fun>(f),_2,_1))
{ return std::bind(std::forward<Fun>(f),_2,_1); }

Вы можете теперь думатьwhy нам нужноstd::function затем.std::function обеспечивает стирание типа для вызываемых объектов с известной подписью. Это, по сути, делает полезным хранить стертые вызовы и писатьvirtual интерфейсы.

Я весьма сочувствую вашим опасениям по поводу ловли ошибок. К сожалению, предложение по концепции (которое могло бы решить эту проблему) не было действительно готово и не было включено в этот пересмотр стандарта. Тем не менее, в настоящее время вы можете получить хорошие ошибки сstatic_assert иis_callable черта (к сожалению, не в стандартной библиотеке, так что у вас естьto write your own).
Decltype в моем примере не является обязательным. Я использовал его, потому что это избавляет меня от необходимости вычислять возвращаемый тип самостоятельно. Если функция имеет много строк, вы можете просто разместить что-то вроде выражения, возвращаемого вdecltype (это все, что имеет значение для вычисления возвращаемого типа в конце концов). Но иногда это становится немного волосатым :(
Спасибо за ваше четкое объяснение. ИМХО, тот факт, что std :: function может использоваться только для хранения, весьма разочаровывает. Если я правильно понимаю ваш ответ, это чтобы избежать накладных расходов? Тогда то же самое относится к умным указателям? Кроме того, в вашемsome_polymorphic_callable Например, я не понимаю, почему функция & lt; & gt; type не может выразить тип для шаблонной лямбды, поскольку функция & lt; & gt; уже может иметь параметры шаблона. Если проблема связана только с накладными расходами, рассматривал ли кто-либо более легкую функцию можно использовать в типах параметров? Это сделало бы STL намного более удобным в использовании. tinlyx
Кроме того, вdecltype Например, если тело функции состоит из 25 строк кода, а не из одной строки, нужно ли включать 25 строк в decltype? Кроме того, использование<typename Fun> выглядит почти как пустота * или макрос для меня. Неправильное использованиеFun может быть обнаружен только при возникновении ошибки компиляции. Для сравнения, функция & lt; & gt; Параметр type (если его можно использовать) четко указывает человеку и компьютеру тип подписи. По общему признанию, у меня очень ограниченные знания о «стирании типов» и тому подобное. Спасибо, что рассказали мне правила. Мне просто интересно, почему так должно быть. tinlyx
@TingL Тип для полиморфной лямбды не может быть выражен, потому чтоfunction<> принимаетone аргумент подписи, в то время как полиморфная лямбда будет иметьan unbounded number of different signatures (именно поэтому он называется полиморфным: он имеет много форм). Проблема с накладными расходами не совсем одинакова для интеллектуальных указателей.unique_ptr действительноzero накладные расходы (это было одной из целей проектирования).shared_ptr может иметь некоторые накладные расходы, если вы не запускаете многопоточную программу, но многопоточные программы - это тот случай, когда ее можно использовать с большей вероятностью.
5
Because function<> employs type erasure. This allows several different function-like types to be stored in a function<>, but incurs a small runtime penalty. Type erasure hides the actual type (your specific lambda) behind a virtual function interface. There is a benefit to this: one of the C++ design "axioms" is to never add overhead unless it is really needed. Using this setup, you do not have any overhead when using type inference (use auto or pass as a template parameter), but you still have all the flexibility to interface with non-template code through function<>. Also note that function<> is not a language construct, but a component of the standard library that can be implemented using simple language features. No, but you can write the function to just take the type of the function (language construct) instead of the specifics of the function<> (library construct). Of course, that makes it a lot harder to actually write down the return type, since it does not directly give you the parameter types. However, using some meta-programming a la Boost.FunctionTypes you can deduce these from the function you pass in. There are some cases where this is not possible though, for example with functors that have a templated operator().

Похожие вопросы