Почему функции лямбда в С++ 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;
}
Ответы
Ответ 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()
.