0

As the title says, how can I have the following intent expressed in code ? The requirements being the function pointer takes any type of arguments. Variadic doesn't work because of std::string.

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

Note that I cannot directly use auto fp = <the lambda> because fp is a class member variable.

#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');
}
puio
  • 1,208
  • 6
  • 19
  • 1
    Template of function pointer is illegal in C++. – NutCracker Jan 07 '21 at 18:53
  • 1
    Can't you just use `auto fp = [](auto a, auto b){ ... };`? Why do you need a function pointer specifically? – Remy Lebeau Jan 07 '21 at 18:56
  • @RemyLebeau it cannot be stored as a class member variable. – puio Jan 07 '21 at 18:57
  • @puio: Yes it can, it's just slightly difficult. You have to take the lambda type as a class parameter. Also, remember that a lambda is merely syntactical sugar, you can just write the class yourself – Mooing Duck Jan 07 '21 at 18:59
  • @MooingDuck and then make the whole class a template ? i don't understand. If that's the case no .. that's no good. – puio Jan 07 '21 at 19:01
  • @puio: I'm curious why not, but the gist is that a function pointer is a type of runtime type erasure, and template member functions require compile time lazy instantiation, and C++ has no way to mix these two concepts. – Mooing Duck Jan 07 '21 at 19:06
  • All the workarounds that come to mind revolve around explicitly listing out every possible combination of `T1 and `T2` in the "interface", which doesn't mix with a function pointer, but can work with a function pointer looking object. – Mooing Duck Jan 07 '21 at 19:09
  • @MooingDuck Such a dilemma.. if i explain everything.. i'd be asked about MVEs.. if i make MVE, i'm asked to explain my whole project.. – puio Jan 07 '21 at 19:09
  • @puio What about making the class member be a `std::function`? – Remy Lebeau Jan 07 '21 at 19:40
  • @RemyLebeau thanks for the hint. Long story short: i'm trying to avoid 1) traditional virtual-based polymorphism, 2) making the whole business logic class a template, while keeping almost everything compile time resolved and inline-able. I thought I could use the idea presented at https://www.youtube.com/watch?v=mU_n_ohIHQk slide 79 -> 83 (lambda + std::any). I came pretty close but can't go further. I'll redesign a few things to move the parameterised function out of the class. I'll try std::function as the last ditch effort, but don't have high hopes. – puio Jan 07 '21 at 19:50
  • `std::function` requires a specific signature, so will require you to specify specific parameter types. – Mooing Duck Jan 07 '21 at 20:51
  • @puio: Wait, is it the same lambda instance every time? `auto lambda = [](){}; using lambdaT = typeof(lambda);` and then in your class have `lambdaT* lambda` member? – Mooing Duck Jan 07 '21 at 20:56
  • @RemyLebeau consider having a look at https://stackoverflow.com/questions/65628159/policy-class-design-but-without-making-the-whole-user-class-a-template – puio Jan 08 '21 at 11:33

2 Answers2

1

A variable template is perfectly fine.
Even the way you initialize them is fine.

Just be aware it is a template for variables, not a variable storing a template (which cannot exist).

Thus when finally using it, things fall apart:

How should the compiler know which of the myriad potential instances you mean?
Simple, you have to actually tell it:

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

Of course, manually desugaring the lambda and getting an instance of it would be more practical:

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

A shame we cannot simply give a lambda a name and let the compiler figure it out.
Brainstorming candidates for appropriate syntax:

struct A = []...;
struct B : []... {
};
using C = []...;
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 1
    Guess i should stop fighting the language now and improve the design itself. – puio Jan 07 '21 at 19:34
  • for naming a lambda, use the same thing the langauge does for `nullptr_t`: `auto lambda = []{}; using lambdaT = typeof(lambda);` – Mooing Duck Jan 07 '21 at 20:53
  • @MooingDuck Yes, but that doesn't work too well outside functions... – Deduplicator Jan 07 '21 at 20:56
  • @MooingDuck i posted a more complete question at https://stackoverflow.com/questions/65628159/policy-class-design-but-without-making-the-whole-user-class-a-template – puio Jan 08 '21 at 11:31
0

You cannot have such a template function pointer, but remember that a lambda is merely syntactical sugar, you can just write the class yourself.

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

And the more generic version would be that you can have the lambda as a member. You have to take the lambda type as a class parameter.

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

The gist is that a function pointer is a type of runtime type erasure, and template member functions require compile time lazy instantiation, and C++ has no way to mix these two concepts. All the workarounds that come to mind revolve around explicitly listing out every possible combination of T1 and T2` in the "interface", which doesn't mix with a function pointer, but can work with a function pointer looking object.

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);
}
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • He *has* such a variable template. Of course, using it is plenty cumbersome, and it either wasn't really what he wanted, he didn't know what he wanted, or he just made a poor choice. At least most probably. – Deduplicator Jan 07 '21 at 19:24
  • Technically he has a template for pointers, which doesn't solve his problem – Mooing Duck Jan 07 '21 at 20:42
  • Hey, i posted a new question with more details: https://stackoverflow.com/questions/65628159/policy-class-design-but-without-making-the-whole-user-class-a-template – puio Jan 08 '21 at 11:32