1

Тема: Конвертувати лямбда у вказівник на функцію

Задача:
Маю клас, екземпляри якого містять дані в своїх полях (поля різного типу). Всі екземпляри мають однакову структуру. Маю масив (вказівник на масив і розмір масиву) таких екземплярів. Хочу зробити функцію, яка буде: в масиві шукати елемент за вказаними значеннями полів екземпляра, і вертати вказівник на знайдений елемент (або nullptr якщо такого елемента не знайдено).

Рішення:
Зробив функцію, яка приймає 3 параметри:

  1. Вказівник на масив: SomeData const *

  2. Розмір масиву: int

  3. Вказівник на функцію, яка визначає чи відповідає елемент заданій умові: bool (*)(SomeData const &)

і сама вона компілюється без проблем.

Не очікувана проблема:
Помилки компіляції появляються, коли я викликаю цю функцію, де 3-тій параметр - lambda.

GetItemByMatch(arr, size, [id](SomeData const &data) -> bool { return data.id == id; });
GetItemByMatch(arr, size, [name](SomeData const &data) -> bool { return !strcmp(data.name, name); });

Коди помилок: C2664, E0413.

Error C2664
'const SomeData *GetItemByMatch(const SomeData *const, const int, bool (__cdecl *const )(const SomeData &))':
cannot convert argument 3
from 'GetItemByName::<lambda_xxxxxxxxxxx(hex-addr)xxxxxxxxxxx>'
to 'bool (__cdecl *const )(const SomeData &)'

Error (active) E0413
no suitable conversion function
from "lambda []bool (const SomeData &data)->bool"
to "bool (*)(const SomeData &)" exists

Мій код (3 файли, мінімальний приклад для відтворення проблеми):

// main.cpp

#include <iostream>
#include "SomeData.hpp"

int main(int const argc, char const *const argv[]) {
    int const size = 3;
    SomeData *arr = new SomeData[size]{
        SomeData(0, "Some name"),
        SomeData(1, "Other name"),
        SomeData(2, "Another name")
    };
    int id = 1;
    SomeData const *ptr = GetItemById(arr, size, id);
    if(ptr) {
        std::cout << *ptr;
        ptr = nullptr;
    }
    else
        std::cout << "Item with id " << id << " was not found.";
    delete[] arr;
    return 0;
}



// SomeData.hpp

#ifndef _SOMEDATA_HPP_
#define _SOMEDATA_HPP_

#include <iostream>

class SomeData {
    private:
    int id;
    char const *name;
    // та інші поля ...

    public:
    SomeData(int id, char const *name);

    friend std::ostream &operator <<(std::ostream &out, SomeData const &data);

    friend SomeData const *GetItemById(SomeData const *const arr, int const size, int const id);
    friend SomeData const *GetItemByName(SomeData const *const arr, int const size, char const *const name);
};

std::ostream &operator <<(std::ostream &out, SomeData const &data);

SomeData const *GetItemById(SomeData const *const arr, int const size, int const id);
SomeData const *GetItemByName(SomeData const *const arr, int const size, char const *const name);
SomeData const *GetItemByMatch(SomeData const *const arr, int const size, bool (*const match)(SomeData const &));

#endif



// SomeData.cpp

#include <iostream>
#include <string.h>
#include "SomeData.hpp"

SomeData::SomeData(int id, char const *name) : id(id), name(name) {}

std::ostream &operator <<(std::ostream &out, SomeData const &data) {
    return out
        << "Id: " << data.id << ", "
        << "Name: \"" << data.name << "\", "
        // та інші поля ...
        << "...";
}

SomeData const *GetItemById(SomeData const *const arr, int const size, int const id) {
    return GetItemByMatch(arr, size, [id](SomeData const &data) -> bool { return data.id == id; });
}
SomeData const *GetItemByName(SomeData const *const arr, int const size, char const *const name) {
    return GetItemByMatch(arr, size, [name](SomeData const &data) -> bool { return !strcmp(data.name, name); });
}
SomeData const *GetItemByMatch(SomeData const *const arr, int const size, bool (*const match)(SomeData const &)) {
    if(!arr || !match)
        return nullptr;
    for(int i = 0; i < size; ++i)
        if(match(arr[i]))
            return arr + i;
    return nullptr;
}

Питання:
Як передавати критерії пошуку (лямбда вирази) у функцію ?

2

Re: Конвертувати лямбда у вказівник на функцію

Замикання - не лямбди. Або викидайте замикання, або використовуйте <functional>.

Подякували: leofun011

3

Re: Конвертувати лямбда у вказівник на функцію

koala написав:

Замикання - не лямбди.

Ох, я вже й забув який це гемор...

koala написав:

Або викидайте замикання, або використовуйте <functional>.

Взяв приклад використання <functional>. Тепер працює.

Рішення:

// SomeData.hpp

#ifndef _SOMEDATA_HPP_
#define _SOMEDATA_HPP_

#include <iostream>
#include <functional>

class SomeData {
    private:
    int id;
    char const *name;
    // та інші поля ...

    public:
    SomeData(int id, char const *name);

    friend std::ostream &operator <<(std::ostream &out, SomeData const &data);

    friend SomeData const *GetItemById(SomeData const *const arr, int const size, int const id);
    friend SomeData const *GetItemByName(SomeData const *const arr, int const size, char const *const name);
};

std::ostream &operator <<(std::ostream &out, SomeData const &data);

SomeData const *GetItemById(SomeData const *const arr, int const size, int const id);
SomeData const *GetItemByName(SomeData const *const arr, int const size, char const *const name);
SomeData const *GetItemByMatch(SomeData const *const arr, int const size, bool (*const match)(SomeData const &));
SomeData const *GetItemByMatch(SomeData const *const arr, int const size, std::function<bool (SomeData const &)> match);

#endif



// SomeData.cpp

#include <iostream>
#include <string.h>
#include <functional>
#include "SomeData.hpp"

SomeData::SomeData(int id, char const *name) : id(id), name(name) {}

std::ostream &operator <<(std::ostream &out, SomeData const &data) {
    return out
        << "Id: " << data.id << ", "
        << "Name: \"" << data.name << "\", "
        // та інші поля ...
        << "...";
}

SomeData const *GetItemById(SomeData const *const arr, int const size, int const id) {
    return GetItemByMatch(arr, size, [id](SomeData const &data) -> bool { return data.id == id; });
}
SomeData const *GetItemByName(SomeData const *const arr, int const size, char const *const name) {
    return GetItemByMatch(arr, size, [name](SomeData const &data) -> bool { return !strcmp(data.name, name); });
}
SomeData const *GetItemByMatch(SomeData const *const arr, int const size, bool (*const match)(SomeData const &)) {
    if(!match)
        return nullptr;
    return GetItemByMatch(arr, size, (std::function<bool (SomeData const &)>)match);
}
SomeData const *GetItemByMatch(SomeData const *const arr, int const size, std::function<bool (SomeData const &)> match) {
    if(!arr || !match)
        return nullptr;
    for(int i = 0; i < size; ++i)
        if(match(arr[i]))
            return arr + i;
    return nullptr;
}

І все таки не розумію, чому для компілятора попередній варіант не допустимий.

Подякували: ch0r_t1

4

Re: Конвертувати лямбда у вказівник на функцію

leofun01 написав:

І все таки не розумію, чому для компілятора попередній варіант не допустимий.

http://eel.is/c++draft/expr.prim.lambda#closure-7 написав:

The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator. [...]

Подякували: leofun011

5

Re: Конвертувати лямбда у вказівник на функцію

Гадаю, тут радше слід дати оце: http://eel.is/c++draft/expr.prim.lambda#closure-1

The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below...
The closure type for a lambda-expression has a public inline function call operator

Лямбда - це не функція, це клас з operator (). Але якщо без замикань - то можна витягнути посилання на функцію. Якщо є замикання - то вказівника явно недостатньо, треба якось замикання передати.

Подякували: leofun011

6

Re: Конвертувати лямбда у вказівник на функцію

koala написав:

Але якщо без замикань - то можна витягнути посилання на функцію. Якщо є замикання - то вказівника явно недостатньо, треба якось замикання передати.

Власне це і говориться в мому посиланні. Не розумію, що вам не підійшло.

Подякували: leofun011

7

Re: Конвертувати лямбда у вказівник на функцію

Те, що питання було не "чому можна без замикань", а "чому не можна з замиканнями".

Подякували: leofun011

8

Re: Конвертувати лямбда у вказівник на функцію

Ну, якщо це так важко, вивести одне з другого, то ок.