C ++ 11 не выводит тип, когда задействованы функции std :: function или lambda - PullRequest
53 голосов
/ 03 апреля 2012

Когда я определяю эту функцию,

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; });, я получаю следующую ошибку:

ошибка: нет соответствующей функции для вызова'filter (std :: set &, main () ::)'

Однако все эти версии do работают:

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

ПочемуC ++ 11 не может угадать тип шаблона, когда я помещаю лямбда-функцию непосредственно в выражение, не создавая std::function?

EDIT:

За советЛюка Дантона в комментариях, вот альтернатива функции, которую я имел ранее, которая не требует явной передачи шаблонов.

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

Ответы [ 3 ]

60 голосов
/ 03 апреля 2012

Вопрос о природе лямбд. Это функциональные объекты с фиксированным набором свойств в соответствии со стандартом, но они являются , а не функцией. Стандарт определяет, что лямбды могут быть преобразованы в std::function<> с точными типами аргументов и, если они не имеют состояния, с указателями на функции.

Но это не значит, что лямбда - это std::function или указатель на функцию. Это уникальные типы, реализующие operator().

С другой стороны, вычитание типов будет выводить только точные типы без преобразований (кроме постоянных / изменчивых квалификаций). Поскольку лямбда не является std::function, компилятор не может определить тип в вызове: filter(mySet,[](int i) { return i%2==0; }); для любого экземпляра std::function<>.

Что касается других примеров, то в первом из них вы конвертируете лямбду в тип функции, а затем передаете ее. Компилятор может определить тип там, как в третьем примере, где std::function является rvalue (временным) того же типа.

Если вы предоставляете инстанцирующий тип int для шаблона, второй рабочий пример, дедукция не вступает в игру, компилятор будет использовать этот тип, а затем преобразует лямбду в соответствующий тип.

14 голосов
/ 29 июня 2015

Забудь о своем деле.поскольку это слишком сложно для анализа.

Возьмите этот простой пример:

 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, что является идентичным случаем - просто выглядит сложным на поверхности.Обратите внимание, что lambdas не относятся к типу std::function - они в основном являются экземплярами сгенерированных компилятором классов (т.е. они являются функторами типов , отличных от std::function).

0 голосов
/ 29 апреля 2019

Если у нас есть:

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)) {};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...