С++ 11 не выводит тип, когда задействованы функции std:: function или лямбда
Когда я определяю эту функцию,
template<class A>
set<A> test(const set<A>& input) {
return input;
}
Я могу назвать это с помощью test(mySet)
другом месте кода без явного определения типа шаблона. Однако, когда я использую следующую функцию:
template<class A>
set<A> filter(const set<A>& input,function<bool(A)> compare) {
set<A> ret;
for(auto it = input.begin(); it != input.end(); it++) {
if(compare(*it)) {
ret.insert(*it);
}
}
return ret;
}
Когда я вызываю эту функцию с помощью filter(mySet,[](int i) { return i%2==0; });
Я получаю следующую ошибку:
error: нет соответствующей функции для вызова "filter" (std :: set &, main(): :)
Однако все эти версии работают:
std::function<bool(int)> func = [](int i) { return i%2 ==0; };
set<int> myNewSet = filter(mySet,func);
set<int> myNewSet = filter<int>(mySet,[](int i) { return i%2==0; });
set<int> myNewSet = filter(mySet,function<bool(int)>([](int i){return i%2==0;}));
Почему С++ 11 не может угадать тип шаблона, когда я помещаю лямбда-функцию непосредственно внутри выражения без прямого создания std::function
?
РЕДАКТИРОВАТЬ:
По рекомендации Люка Дантона в комментариях, здесь есть альтернатива функции, которую я раньше, которая не нуждается в шаблонах, которые должны быть переданы явно.
template<class A,class CompareFunction>
set<A> filter(const set<A>& input,CompareFunction compare) {
set<A> ret;
for(auto it = input.begin(); it != input.end(); it++) {
if(compare(*it)) {
ret.insert(*it);
}
}
return ret;
}
Это можно вызвать с помощью set<int> result = filter(myIntSet,[](int i) { я % 2 == 0; });
без шаблона.
Компилятор может даже догадываться о типах возврата в некоторой степени, используя новое ключевое слово decltype и используя синтаксис типа возвращаемого нового типа. Вот пример, который преобразует набор в карту, используя одну функцию фильтрации и одну функцию, которая генерирует ключи на основе значений:
template<class Value,class CompareType,class IndexType>
auto filter(const set<Value>& input,CompareType compare,IndexType index) -> map<decltype(index(*(input.begin()))),Value> {
map<decltype(index(*(input.begin()))),Value> ret;
for(auto it = input.begin(); it != input.end(); it++) {
if(compare(*it)) {
ret[index(*it)] = *it;
}
}
return ret;
}
Его также можно вызывать без непосредственного использования шаблона, так как
map<string,int> s = filter(myIntSet,[](int i) { return i%2==0; },[](int i) { return toString(i); });
Ответы
Ответ 1
Вопрос касается характера лямбда. Это функциональные объекты с фиксированным набором свойств в соответствии со стандартом, но они не являются функцией. Стандарт определяет, что lambdas можно преобразовать в std::function<>
с точными типами аргументов и, если они не имеют состояния, указатели на функции.
Но это не означает, что лямбда является std::function
или указателем на функцию. Это уникальные типы, реализующие operator()
.
Тип дедукции, с другой стороны, будет выводить только точные типы без конверсий (кроме const/volatile qualifications). Поскольку лямбда не является std::function
, компилятор не может вывести тип в вызове: filter(mySet,[](int i) { return i%2==0; });
для любого экземпляра std::function<>
.
Как и в других примерах, в первом случае вы преобразовываете лямбда в тип функции, а затем передаете это. Компилятор может выводить тип там, как в третьем примере, где std::function
является rvalue (временным) того же типа.
Если вы укажете шаблон-экземпляр int
для шаблона, второй рабочий пример, дедукция не вступает в игру, компилятор будет использовать этот тип, а затем преобразовать лямбда в соответствующий тип.
Ответ 2
Забудьте о своем случае. поскольку это слишком сложно для анализа.
Возьмем этот простой пример:
template<typename T>
struct X
{
X(T data) {}
};
template<typename T>
void f(X<T> x) {}
Теперь вызовите f
как:
f(10);
Здесь может возникнуть соблазн подумать, что T
будет выводиться на int
и, поэтому вышеупомянутый вызов функции должен работать. Ну, это не так. Чтобы все было просто, представьте, что есть другой конструктор, который принимает int
как:
template<typename T>
struct X
{
X(T data) {}
X(int data) {} //another constructor
};
Теперь, что нужно сделать T
, когда пишу f(10)
? Ну, T
может любой тип.
Обратите внимание, что может быть много других подобных случаев. Возьмите эту специализацию, например:
template<typename T>
struct X<T*> //specialized for pointers
{
X(int data) {};
};
Теперь, что нужно сделать T
для вызова f(10)
? Теперь это кажется еще сложнее.
Следовательно, это не выводимый контекст, который объясняет, почему ваш код не работает для std::function
, который является идентичным case — просто выглядит сложным на поверхности. Обратите внимание, что lambdas не относятся к типу std::function
— они в основном являются экземплярами классов, сгенерированных компилятором (т.е. они являются функторами разных типов, чем std::function
).
Ответ 3
Если у нас есть:
template <typename R, typename T>
int myfunc(std::function<R(T)> lambda)
{
return lambda(2);
}
int r = myfunc([](int i) { return i + 1; });
Это не скомпилируется. Но если вы ранее заявили:
template <typename Func, typename Arg1>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1));
template <typename Func>
int myfunc(Func lambda)
{
return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
}
Вы можете вызвать вашу функцию с лямбда-параметром без проблем.
Здесь есть 2 новых кода.
Во-первых, у нас есть объявление функции, которое полезно только для возврата типа указателя на функцию старого стиля на основе заданных параметров шаблона:
template <typename Func, typename Arg1>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1)) {};
Во-вторых, у нас есть функция, которая принимает аргумент шаблона для построения нашего ожидаемого лямбда-типа, вызывая 'getFuncType':
template <typename Func>
int myfunc(Func lambda)
{
return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
}
С правильными параметрами шаблона, теперь мы можем назвать настоящий "myfunc". Полный код будет:
template <typename R, typename T>
int myfunc(std::function<R(T)> lambda)
{
return lambda(2);
}
template <typename Func, typename Arg1>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1)) {};
template <typename Func>
int myfunc(Func lambda)
{
return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
}
int r = myfunc([](int i) { return i + 1; });
Вы можете объявить любую перегрузку для getFuncType в соответствии с вашим лямбда-параметром. Например:
template <typename Func, typename Arg1, typename Arg2>
static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr, Arg2* arg2 = nullptr) -> decltype((*func)(*arg1, *arg2)) {};