Timo
Timo

Reputation: 881

Constexpr constructible function object

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?

Upvotes: 3

Views: 775

Answers (2)

n314159
n314159

Reputation: 5095

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.

Upvotes: 2

bolov
bolov

Reputation: 75853

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

Upvotes: 0

Related Questions