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

С++ 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); });
4b9b3361

Ответ 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)) {};