Подтвердить что ты не робот

Почему функции лямбда в С++ 11 не имеют функции <> типов?

Я играю с функциональными возможностями С++ 11. Одна вещь, которую я нахожу странной, заключается в том, что тип лямбда-функции на самом деле НЕ является функцией < > type. Что еще, лямбда, похоже, не очень хорошо работает с механизмом ввода-вывода.

Приложен небольшой пример, в котором я тестировал перелистывание двух аргументов функции для добавления двух целых чисел. (Компилятор, который я использовал, был gcc 4.6.2 под MinGW.) В этом примере тип для addInt_f был явно определен с использованием функции < > while addInt_l - это лямбда, тип которой имеет тип-вывод, с помощью auto.

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

Следующие несколько строк показывают, что лямбда-версия (а также "сырая" версия) может быть принята, если она явно передана соответствующей функции < > type.

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

  • Почему именно лямбда-функция не имеет типа function<> в первую очередь? В маленьком примере, почему addInt_l не имеет function<int (int,int)> как типа вместо другого типа lambda? С точки зрения функционального программирования, какая разница между функционально-функциональным объектом и лямбдой?

  • Если есть фундаментальная причина, что эти два должны быть разными. Я слышал, что лямбда может быть преобразована в function<>, но они разные. Является ли это проблемой/недостатком дизайна С++ 11, проблемой реализации или есть ли преимущество в том, чтобы различать два, как это есть? Кажется, что сигнатура типа addInt_l сама по себе предоставила достаточную информацию о параметрах и возвращаемых типах функции.

  • Есть ли способ написать лямбда, чтобы избежать вышеупомянутого явного литья типов?

Спасибо заранее.

    //-- 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;
    }

4b9b3361

Ответ 1

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

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

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

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

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

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


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

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

Решение? Не запрещайте своим абонентам std::function. Принять любой вызываемый.

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); }

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

Ответ 2

  • Потому что function<> использует тип стирания. Это позволяет хранить несколько различных типов функций в function<>, но требует небольшого штрафа за выполнение. Тип стирания скрывает фактический тип (ваш конкретный лямбда) за интерфейсом виртуальных функций.
  • Для этого есть преимущество: одна из концепций С++ "аксиомы" заключается в том, чтобы никогда не добавлять накладные расходы, если это действительно необходимо. Используя эту настройку, у вас нет накладных расходов при использовании вывода типа (используйте auto или передайте в качестве параметра шаблона), но у вас все еще есть все возможности для взаимодействия с кодом без шаблона через function<>. Также обратите внимание, что function<> не является языковой конструкцией, а компонентом стандартной библиотеки, которая может быть реализована с использованием простых языковых функций.
  • Нет, но вы можете написать функцию, чтобы просто взять тип функции (языковая конструкция), а не специфика function<> (библиотека). Конечно, это делает намного сложнее записать тип возврата, так как он не дает вам напрямую типы параметров. Однако, используя некоторые метапрограммы a la Boost.FunctionTypes, вы можете вывести их из функции, в которую вы проходите. Есть случаи, когда это невозможно, например, с функторами с шаблоном operator().