Как получить указатель функции на общую лямбду?

Как следует из названия, как я могу выразить следующее намерение в коде? Требования, являющиеся указателем на функцию, принимают аргументы любого типа. Variadic не работает из-за std::string.

https://godbolt.org/z/1E1szT

Обратите внимание, что я не могу напрямую использовать auto fp = <the lambda>, потому что fp является переменной-членом класса.

#include <iostream>

template<typename T1, typename T2>
void(*fp)(T1 t1, T2 t2) = [](auto a, auto b){
                              std::cout << a << "--"
                              << b << std::endl;
                          };

int main(){
    fp(1, 2);
    fp('a', 'b');
}

person puio    schedule 07.01.2021    source источник
comment
Шаблон указателя на функцию недопустим в C++.   -  person NutCracker    schedule 07.01.2021
comment
Разве вы не можете просто использовать auto fp = [](auto a, auto b){ ... };? Зачем вам нужен указатель на функцию?   -  person Remy Lebeau    schedule 07.01.2021
comment
@RemyLebeau нельзя хранить как переменную-член класса.   -  person puio    schedule 07.01.2021
comment
@puio: Да, может, просто это немного сложно. Вы должны взять тип лямбда в качестве параметра класса. Кроме того, помните, что лямбда — это просто синтаксический сахар, вы можете просто написать класс самостоятельно.   -  person Mooing Duck    schedule 07.01.2021
comment
@MooingDuck, а затем сделать весь класс шаблоном? я не понимаю. Если это так, нет.. это нехорошо.   -  person puio    schedule 07.01.2021
comment
@puio: мне любопытно, почему бы и нет, но суть в том, что указатель на функцию является типом стирания типа во время выполнения, а функции-члены шаблона требуют ленивого создания экземпляров во время компиляции, а C ++ не может смешивать эти две концепции.   -  person Mooing Duck    schedule 07.01.2021
comment
Все обходные пути, которые приходят на ум, вращаются вокруг явного перечисления всех возможных комбинаций T1 and T2` в интерфейсе, который не смешивается с указателем функции, но может работать с объектом, выглядящим как указатель функции.   -  person Mooing Duck    schedule 07.01.2021
comment
@MooingDuck Такая дилемма... если я все объясню... меня спросят о MVE... если я сделаю MVE, меня попросят объяснить весь мой проект...   -  person puio    schedule 07.01.2021
comment
@puio Как насчет того, чтобы сделать член класса std::function?   -  person Remy Lebeau    schedule 07.01.2021
comment
@RemyLebeau спасибо за подсказку. Короче говоря: я пытаюсь избежать 1) традиционного виртуального полиморфизма, 2) превращения всего класса бизнес-логики в шаблон, сохраняя при этом почти все время компиляции разрешенным и встроенным. Я подумал, что могу использовать идею, представленную на youtube.com/watch?v=mU_n_ohIHQk. слайд 79 -> 83 (лямбда + std::any). Я подошел довольно близко, но не могу идти дальше. Я изменю несколько вещей, чтобы вывести параметризованную функцию из класса. Я попробую std::function в качестве последней попытки, но не возлагаю больших надежд.   -  person puio    schedule 07.01.2021
comment
std::function требует определенной подписи, поэтому вам потребуется указать определенные типы параметров.   -  person Mooing Duck    schedule 07.01.2021
comment
@puio: Подождите, это каждый раз один и тот же экземпляр лямбда? auto lambda = [](){}; using lambdaT = typeof(lambda); а потом в вашем классе есть lambdaT* lambda участника?   -  person Mooing Duck    schedule 07.01.2021
comment
@RemyLebeau рассмотрите возможность взглянуть на stackoverflow.com/questions/65628159/   -  person puio    schedule 08.01.2021


Ответы (2)


Шаблон переменных — это прекрасно.
Даже способ их инициализации хорош.

Просто имейте в виду, что это шаблон для переменных, а не переменная, хранящая шаблон (который не может существовать).

Таким образом, при его окончательном использовании все разваливается:

Как компилятору узнать, какой из бесчисленного множества потенциальных экземпляров вы имеете в виду?
Все просто: вам нужно фактически рассказать:

int main(){
    fp<int, int>(1, 2);
    fp<char, char>('a', 'b');
}

Конечно, ручное удаление сахара из лямбды и получение ее экземпляра было бы более практичным:

struct {
    template <class T1, class T2>
    void operator()(T1 a, T2 b) const {
        std::cout << a << "--" << b << std::endl;
    };
} fp;

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

struct A = []...;
struct B : []... {
};
using C = []...;
person Deduplicator    schedule 07.01.2021
comment
Думаю, мне следует перестать бороться с языком и улучшить сам дизайн. - person puio; 07.01.2021
comment
для именования лямбды используйте то же самое, что язык делает для nullptr_t: auto lambda = []{}; using lambdaT = typeof(lambda); - person Mooing Duck; 07.01.2021
comment
@MooingDuck Да, но это не очень хорошо работает вне функций... - person Deduplicator; 07.01.2021
comment
@MooingDuck я разместил более полный вопрос по адресу stackoverflow.com/questions/65628159/ - person puio; 08.01.2021

У вас не может быть такого указателя шаблонной функции, но помните, что лямбда — это просто синтаксический сахар, вы можете просто написать класс самостоятельно.

class fp {
    template<class AT, class BT>
    void operator()(AT&& a, BT&& b) {
        std::cout << a << "--" << b << std::endl; 
    };
};
class class_with_functionoid_member {
    fp fn_;
};

И более общая версия будет заключаться в том, что вы можете иметь лямбду в качестве члена. Вы должны взять тип лямбда в качестве параметра класса.

template<class fnT> 
class class_with_lambda_member {
    fnT fn_;
public:
    class_with_lambda_member(fnT fn) : fn_(std::move(fn)) {}
};

Суть в том, что указатель на функцию — это тип стирания типа во время выполнения, а функции-члены шаблона требуют отложенного создания экземпляров во время компиляции, а C++ не может смешивать эти две концепции. Все обходные пути, которые приходят на ум, вращаются вокруг явного перечисления всех возможных комбинаций T1 и T2` в интерфейсе, который не смешивается с указателем на функцию, но может работать с объектом, выглядящим как указатель на функцию.

struct erasable_functions {
    virtual ~erasable_functions(){}
    virtual void operator()(int, int)=0;
    virtual void operator()(int, char)=0;
    virtual void operator()(char, int)=0;
    virtual void operator()(char, char)=0;
};

template<class lambdaT>
struct erased_functions : erasable_functions {
    lambdaT lambda_;
    erased_functions(lambdaT lambda) : lambda_(std::move(lambda)){}
    virtual void operator()(int a, int b) {lambda_(a, b);
    virtual void operator()(int a, char b) {lambda_(a, b);
    virtual void operator()(char a, int b) {lambda_(a, b);
    virtual void operator()(char a, char b) {lambda_(a, b);
};
template<class lambdaT>
erased_functions<lambdaT> erase_functions(lambdaT lambda)
{return {std::move(lambda)};}

struct class_with_functionoid_member {
    erasable_functions* functions_;
    class_with_functionoid_member(erasable_functions* functions) : functions_(functions){}
    void operator()(int a, int b) {(*functions_)(a, b);
    void operator()(int a, char b) {(*functions_)(a, b);
    void operator()(char a, int b) {(*functions_)(a, b);
    void operator()(char a, char b) {(*functions_)(a, b);
};

int main() {
    auto lambda = erase_functions([](auto a, auto b) {
            std::cout << a << "--" << b << std::endl;
        };
    class_with_functionoid_member c(&lambda);
}
person Mooing Duck    schedule 07.01.2021
comment
У него есть такой шаблон переменных. Конечно, использовать его довольно обременительно, и либо это было не совсем то, что он хотел, либо он не знал, чего хотел, либо он просто сделал плохой выбор. По крайней мере, скорее всего. - person Deduplicator; 07.01.2021
comment
Технически у него есть шаблон для указателей, который не решает его проблему. - person Mooing Duck; 07.01.2021
comment
Эй, я опубликовал новый вопрос с более подробной информацией: stackoverflow.com/questions/65628159/ - person puio; 08.01.2021