3

I have a question somewhat similar to this one, but for a more limited case which I believe should be possible somehow: I want to construct a static constexpr array of function calls from a number of lambdas, each sharing the same signature. The static and constexpr part is important here since I'm on an embedded system, where I want to make sure such tables end up in Flash.

So basically what I want to do is

#include<vector>
#include<functional>
#include<variant>

using params_t = std::vector<std::variant<int, float /*maybe others*/ >>;

struct command_t {
   using callable_t = std::function<void(params_t)>;

  const callable_t func;
   //other members..
};

class AClass {
    template<typename func_t>
    constexpr static command_t::callable_t make_callable(func_t fun) {
         return [fun](params_t params){/*construct a call to fun using params and template magic*/};
    }

    static void mycommand();
    static void mycommand2(int i);

    //The following fails: 
    ///"error: in-class initialization of static data member 'const command_t AClass::commands [2]' of non-literal type"
    static constexpr command_t commands[2] = {command_t{make_callable(mycommand)},
                                              command_t{make_callable(mycommand2)}};
};

On coliru

Note that the type erasure here is quite limited, since the signature of the lambda varies only by the signature of the capture of fun. The function call obviously doesn't (and cannot) need to be constexpr, only the construction.

So basically my question is can I somehow make the commands array static constexpr, either somehow using std::function, or something like inplace_function, or perhaps by spinning my own code for type-erasing the lambda in this specific case?

Timo
  • 739
  • 1
  • 6
  • 13
  • 1
    Lambas without a capture can be converted to standard function pointers. That also means you can get rid of the `std::function` objects, which aren't constexpr. So you'd have `using callable_t = void (*)(params_t);`. I'm not sure what the `make_callable` is supposed to do. – AVH Mar 05 '20 at 13:24
  • 1
    @Darhuuk Note that the lambda does have a capture. `make_callable` is in reality a template: `fun` can have any number and types of arguments, and `make_callable` constructs a lambda which calls `fun` with params coming from the vector of variants given as an argument (which obviously must match the signature of `fun`, which is checked at runtime). – Timo Mar 05 '20 at 13:28
  • Ah, I see. So is the goal to "automate" this? Because it seems to me that if you write out each lambda by hand, there's no need to capture `fun` (since they're static functions). – AVH Mar 05 '20 at 13:54
  • @Darhuuk Exactly! – Timo Mar 05 '20 at 14:06

2 Answers2

2

In general, this is not possible since the capture of a lambda can get arbitrary large and hence, at some point we need a heap allocation which then kills any hopes of constexpr pre-C++20 (and I don't think C++20 will help much for this case, either).

But you only want to capture a function pointer if I see this right and that we can do:

#include <vector>
 #include<variant>

using params_t = std::vector<std::variant<int, float /*maybe others*/ >>;

struct command_t {
   using callable_t = void (*)(std::vector<params_t>); 

  const callable_t func;
   //other members..
};

 template<auto f> 
 void wrap(std::vector<params_t>){
    // make this dependent of f, maybe use function_traits for fancy stuff
 }
class AClass {

    static void mycommand();
    static void mycommand2(int i);

    static constexpr command_t commands[2] = {wrap<mycommand>, wrap<mycommand2>};
};

 int main() {

 }

Thanks to xskxzr for valuable suggestions.

n314159
  • 4,990
  • 1
  • 5
  • 20
  • That looks promising, I'll give it a go tomorrow! I think you shouldn't have `&` in front of the function name when calling do_wrap? – Timo Mar 05 '20 at 14:48
  • When `f` is declared as a function parameter, it can't be known at compile time, so it can't be used as a template argument. This is the reason why your `do_wrap` does not work. By the way, why do you need the `wrapper` class? Is there any problem to declare `template void wrap(std::vector);` directly? – xskxzr Mar 05 '20 at 16:30
  • `auto f` is C++20 and we want to extract the arguments, I think. – n314159 Mar 05 '20 at 21:56
  • It's C++17, and you can also extract `Args...` using [`function_traits`](https://stackoverflow.com/a/9065203/5376789) on `decltype(f)`. – xskxzr Mar 06 '20 at 02:45
  • Ah, then I had something wrong in mind. Yes this simplifies stuff. I will rework the answer. – n314159 Mar 06 '20 at 06:00
  • It works, thanks! The `auto f` caused some trouble as I had to rewrite the "magic" part which extracts all the function stuff, but it's done now. – Timo Mar 06 '20 at 08:55
0

Since mycommanN have different signatures and you need to capture them, I don't see a way to have a constexpr vector. Maybe someone can come up with a better design.

I have a solution: use std::tuple. But I don't really like as it is really cumbersome to work with tuple as a container. For instance iterating over it is ... let's say not a walk in the park. Anyway, here it is in case it does help:

using params_t = std::vector<std::variant<int, float /*maybe others*/>>;

// I needed to lift this out of AClass because of ... complicated reasons
// (short version: when both are AClass members
//    the return type of `make_command` is not resolved in the init of `commands`
//    because both are static, `commands` is not a template and `make_command` is a template
//    tbh I don't know exactly what is happening. It's one of those dark corners of C++)

template <class RealFunc>
static constexpr auto make_command(RealFunc real_func) {
  return [real_func](params_t params) { /*magic*/ };
}

struct AClass {
  static void mycommand();
  static void mycommand2(int i);

  static constexpr std::tuple commands{make_command(mycommand),
                                       make_command(mycommand2)};
};

// usage
auto test() {
  constexpr auto command0 = std::get<0>(AClass::commands<>);

  params_t params0 = {};

  return command0(command0);
}
bolov
  • 72,283
  • 15
  • 145
  • 224
  • Tuple doesn't work, because then the code using the commands table would be dependent on the types of all the possible commands, and that's what I want to avoid. – Timo Mar 06 '20 at 09:08