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

Передача лямбды в шаблон функции

Я изучаю С++, и я пытаюсь реализовать двоичную функцию поиска, которая находит первый элемент, для которого выполняется предикат. Первый аргумент функции - это вектор, а второй аргумент - это функция, которая оценивает предикат для данного элемента. Функция двоичного поиска выглядит следующим образом:

template <typename T> int binsearch(const std::vector<T> &ts, bool (*predicate)(T)) {
    ...
}

Это работает как ожидалось, если используется так:

bool gte(int x) {
    return x >= 5;
}

int main(int argc, char** argv) {
    std::vector<int> a = {1, 2, 3};
    binsearch(a, gte);
    return 0;
}

Но если я использую функцию лямбда как предикат, я получаю ошибку компилятора:

search-for-a-range.cpp:20:5: error: no matching function for call to 'binsearch'
    binsearch(a, [](int e) -> bool { return e >= 5; });
    ^~~~~~~~~
search-for-a-range.cpp:6:27: note: candidate template ignored: could not match 'bool (*)(T)' against '(lambda at
      search-for-a-range.cpp:20:18)'
template <typename T> int binsearch(const std::vector<T> &ts,
                          ^
1 error generated.

Вышеприведенная ошибка генерируется

binsearch(a, [](int e) -> bool { return e >= 5; });

Что случилось? Почему компилятор не убежден в том, что моя лямбда имеет правильный тип?

4b9b3361

Ответ 1

Ваша функция binsearch принимает указатель на функцию в качестве аргумента. A lambda, а указатель функции - это разные типы: лямбда может рассматриваться как экземпляр реализации структуры operator().

Обратите внимание, что без привязки lambdas (lambdas, которые не фиксируют какую-либо переменную) неявно конвертируются в указатель функций. Здесь неявное преобразование не работает из-за замены шаблона:

#include <iostream>

template <typename T>
void call_predicate(const T& v, void (*predicate)(T)) {
    std::cout << "template" << std::endl;
    predicate(v);
}

void call_predicate(const int& v, void (*predicate)(int)) {
    std::cout << "overload" << std::endl;
    predicate(v);
}

void foo(double v) {
    std::cout << v << std::endl;
}

int main() {
    // compiles and calls template function
    call_predicate(42.0, foo);

    // compiles and calls overload with implicit conversion
    call_predicate(42, [](int v){std::cout << v << std::endl;});

    // doesn't compile because template substitution fails
    //call_predicate(42.0, [](double v){std::cout << v << std::endl;});

    // compiles and calls template function through explicit instantiation
    call_predicate<double>(42.0, [](double v){std::cout << v << std::endl;});
}

Вы должны сделать свою функцию binsearch более общей, что-то вроде:

template <typename T, typename Predicate>
T binsearch(const std::vector<T> &ts, Predicate p) {

    // usage

    for(auto& t : ts)
    {
        if(p(t)) return t;
    }

    // default value if p always returned false

    return T{};
}

Возьмите вдохновение из .

Ответ 2

lambda expression с пустым списком захвата может быть неявно преобразован в указатель функции. Но указатель функции predicate принимает T как свой параметр, который нужно вывести. Преобразование типа не будет учитываться в выводе типа шаблона, T не может быть выведено; как указано в сообщении об ошибке, шаблон-кандидат (т.е. binsearch) игнорируется.

Вы можете использовать operator+ для достижения этого, он преобразует лямбда в указатель функции, который будет передан на binsearch позже, а затем T будет успешно выведен [1].

binsearch(a, +[](int e) -> bool { return e >= 5; });
//           ~

Конечно, вы можете явно использовать static_cast:

binsearch(a, static_cast<bool(*)(int)>([](int e) -> bool { return e >= 5; }));

Обратите внимание, что если вы измените тип predicate, который не зависит от T, т.е. bool (*predicate)(int), прохождение лямбда с пустым списком записей тоже будет работать; выражение лямбда будет неявно преобразовываться в указатель функции.


Другим решением является изменение типа параметра из указателя функции на std::function, что является более общим для функторов:

template <typename T> int binsearch(const std::vector<T> &ts, std::function<bool (typename std::vector<T>::value_type)> predicate) {
    ...
}

затем

binsearch(a, [](int e) -> bool { return e >= 5; });

[1] Положительная лямбда: '+ [] {}' - Какое колдовство это?

Ответ 3

Почему компилятор не убежден в том, что моя лямбда имеет нужный тип?

Функции шаблона, которые говорят, чтобы выводить их параметры шаблона, не конвертируются. Лямбда не является указателем на функцию, поэтому нет способа вывести T в этот аргумент. Поскольку все аргументы функции самостоятельно выводят свои параметры шаблона (если исключение не заблокировано), это приводит к ошибке.

Есть ряд исправлений, которые вы можете сделать.

Вы можете исправить функцию шаблона.

template <class T>
int binsearch(const std::vector<T> &ts, bool (*predicate)(T))

Замените указатель на функцию Predicate predicate или Predicate&& predicate и оставите тело без изменений.

template <class T, class Predicate>
int binsearch(const std::vector<T> &ts, Predicate&& predicate)

Использовать блокировку вычета:

template<class T>struct tag_t{using type=T;};
template<class T>using block_deduction=typename tag_t<T>::type;
template <class T>
int binsearch(const std::vector<T> &ts, block_deduction<bool (*)(T)> predicate)

при замене указателя функции на std::function<bool(T)>.

Вы можете исправить это на сайте вызова.

Вы можете передать T вручную binsearch<T>(vec, [](int x){return x<0;}).

Вы можете разложить лямбда на указатель функции, поставив перед ней + +[](int x)... или static_cast<bool(*)(int)>(... ).

Лучшим вариантом является Predicate one. Это также то, что делает стандартный код библиотеки.

Мы также можем сделать еще один шаг и сделать ваш код еще более общим:

template <class Range, class Predicate>
auto binsearch(const Range &ts, Predicate&& predicate)
-> typename std::decay< decltype(*std::begin(ts)) >::type

Часть -> typename std::decay... trailing return type может быть удалена в С++ 14.

Преимущество этого в том, что если тело также использует std::begin и std::end для поиска итераторов begin/end, binsearch теперь поддерживает deque s, плоские массивы C-стиля, std::array s, std::string s, std::vector s и даже некоторые пользовательские типы.

Ответ 4

Если у вас есть контроль над binsearch, я предлагаю вам его реорганизовать:

template <typename T, typename Predicate>
int binsearch(std::vector<T> const& vec, Predicate&& pred) {
    // This is just to illustrate how to call pred
    for (auto const& el : vec) {
        if (pred(el)) {
            // Do something
        }
    }
    return 0; // Or anything meaningful
}

Другим способом для вас является выполнение стирания стилей на объектах-функторах/указателях функций/независимо... путем вложения их в std::function<bool(T const&)>. Для этого просто перепишите вышеприведенную функцию как:

template <typename T>
int binsearch(std::vector<T> const& vec, std::function<bool(T const&)> pred);

Но поскольку вывод аргумента шаблона не выполняет никакого преобразования, вам необходимо явно передать свою функцию следующим образом:

auto my_predicate = [](int x) { return true; }; // Replace with actual predicate
std::vector<int> my_vector = {1, 2, 3, 4};
binsearch(my_vector, std::function<bool (int const&)>(my_predicate));

Однако, учитывая описание вашей функции, кажется, что она выполняет ту же работу, что и std::find_if.

std::vector<int> my_vector = {1, 12, 15, 13, 16};
auto it = std::find_if(std::begin(my_vector), std::end(my_vector),
                       [](int vec_el) { return !vec_el%5; });
// it holds the first element in my_vector that is a multiple of 5.
if (it != std::end(my_vector)) {
    std::cout << *it << std::endl; // prints 15 in this case
}

Обратите внимание, что для выполнения бинарного поиска вам нужно больше, чем просто предикат: вам нужен предикат, который определяет порядок в вашем диапазоне И целевое значение.

Ответ 5

Указатель функции и лямбда-функция - это не одно и то же.

Объекту t нельзя присваивать предикату, где:

 bool (*predicate)(int)

и

auto t = [](int e) -> bool { return e >= 5; });

Можно также использовать std::function<bool(int)>. Ваша подпись будет выглядеть так:

template <typename T> 
int binsearch(const std::vector<T> &ts, std::function<bool(T)> predicate){
   // ...
}

Теперь, когда это не указатель на функцию, вам нужно будет привязать указатели к вашим словам, если они необходимы (я полагаю, вы в порядке с только лямбдами)