1

I want to create a map whose key is a string and whose value is one of the member functions of a class. I originally had the functions outside the class and that could be called from a function in the class.

void nvmRead(unsigned char* msg, unsigned char* resp_frame);
void nvmWrite(unsigned char* msg, unsigned char* resp_frame);
void nvmProvision(unsigned char* msg, unsigned char* resp_frame);
void nvmStatus(unsigned char* msg, unsigned char* resp_frame);

std::map<std::string, void (*)(unsigned char* msg, unsigned char* resp_frame)> command_table = {
  { "NVM_READ", nvmRead },
  { "NVM_WRITE", nvmWrite },
  { "NVM_PROVISION", nvmProvision },
  { "NVM_STATUS", nvmStatus }
};

Then from a function in my class that receives a command request corresponding to one of the keys in the s variable it jumps to the appropriate function. I call the function using:

command_table.at(s)(parm1, parm2);

That got me to the appropriate function when they were defined external to the class.

I am trying to do the same thing but I want the above functions to be within the class, so that it can access private members of the class.

If I move the above lines of code into the class in the public section it seems to complain about not being able to convert the functions:

 error: could not convert '{{"NVM_READ", ((myns::RaidDevCommInTask*)this)->myns::RaidDevCommInTask::nvmRead}, {"NVM_WRITE", ((myns::RaidDevCommInTask*)this)->myns::RaidDevCommInTask::nvmWrite}, {"NVM_PROVISION", ((myns::RaidDevCommInTask*)this)->myns::RaidDevCommInTask::nvmProvision}, {"NVM_STATUS", ((myna::RaidDevCommInTask*)this)->myns::RaidDevCommInTask::nvmStatus}}' from '<brace-enclosed initializer list>' to 'std::map<std::__cxx11::basic_string<char>, void (*)(unsigned char*, unsigned char*)>'
   85 |   };

I have tried various combinations that I won't go into here. Including making the assignments in a constructor doing something along the lines of

command_table["NVM_READ"] = nvmRead

which generates this error:

src/tests/raid_dev_comm_in_task.cpp:40:31: error: cannot convert 'myns::RaidDevCommInTask::nvmRead' from type 'void (myns::RaidDevCommInTask::)(unsigned char*, unsigned char*)' to type 'std::map<std::__cxx11::basic_string<char>, void (*)(unsigned char*, unsigned char*)>::mapped_type' {aka 'void (*)(unsigned char*, unsigned char*)'}
   40 |   command_table["NVM_READ"] = nvmRead;
      |                               ^~~~~~~

Again trying lots of different combinations that I won't go into here.

I can't seem to find the correct combination.

How can I make a map where a string is used as the key and the value is a class' member function within the class?

chriskot
  • 21
  • 2

4 Answers4

1

Instead of simple function pointers, you could store pointers-to-member-functions. Note that the syntax for calling them is a bit different, since they require an object to work on:

using MemFnT = void(RaidDevCommInTask::*)(unsigned char*, unsigned char*);
std::map<std::string, MemFnT> command_table = {
  { "NVM_READ", &RaidDevCommInTask::nvmRead },
  { "NVM_WRITE", &RaidDevCommInTask::nvmWrite },
  { "NVM_PROVISION", &RaidDevCommInTask::nvmProvision },
  { "NVM_STATUS", &RaidDevCommInTask::nvmStatus }
};

Then when you call them, you'll do something like:

MemFnT command = command_table.at(s);
(this->*command)(param1, param2);
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
0

If you can use C++11, the simplest answer would be to use std::bind and std::function.

Something like this:

void nvmRead(unsigned char* msg, unsigned char* resp_frame);
void nvmWrite(unsigned char* msg, unsigned char* resp_frame);
void nvmProvision(unsigned char* msg, unsigned char* resp_frame);
void nvmStatus(unsigned char* msg, unsigned char* resp_frame);

std::map<std::string, std::function<void(unsigned char*, unsigned char*)>> command_table = {
  { "NVM_READ", std::bind(&RaidDevCommInTask::nvmRead, this, _1, _2) },
  { "NVM_WRITE", std::bind(&RaidDevCommInTask::nvmWrite, this, _1, _2) },
  { "NVM_PROVISION", std::bind(&RaidDevCommInTask::nvmProvision, this, _1, _2) },
  { "NVM_STATUS", std::bind(&RaidDevCommInTask::nvmStatus, this, _1, _2) }
};

Note that this creates an instance of command_table for each instance of the class, which is often undesirable.

Also, std::function can be more expensive, so if you need performance, it'd be better just to have something like:

enum class Command {
    NVM_READ,
    NVM_WRITE,
    NVM_PROVISION,
    NVM_STATUS,
};

static std::map<std::string, Command> command_table = {
  { "NVM_READ", Command::NVM_READ },
  { "NVM_WRITE", Command::NVM_WRITE },
  { "NVM_PROVISION", Command::NVM_PROVISION },
  { "NVM_STATUS", Command::NVM_STATUS },
};

void nvmCommand(const std::string& command, unsigned char* msg, unsigned char* resp_frame)
{
    switch (command)
    {
    case Command::NVM_READ:
        return nvmRead(msg, resp_frame);
    case Command::NVM_WRITE:
        /* ... */
    }
}
LHLaurini
  • 1,737
  • 17
  • 31
  • 1
    Your first solution requires a map for each instance, where as the member function pointer solutions only need 1 map for the type. Those solutions also circumvent any performance problem with `std::function` you mention. – François Andrieux Sep 21 '22 at 20:52
  • @FrançoisAndrieux I did say simplest, not best, but I should probably make that clearer. The second version should be static though, thanks. – LHLaurini Sep 21 '22 at 20:58
0

You could make a lookup table of member functions:

struct RaidDevCommInTask {

    void call(std::string cmd, unsigned char* msg, unsigned char* resp_frame) {
        static std::unordered_map<std::string,
          void(RaidDevCommInTask::*)(unsigned char*, unsigned char*)> command_table
        {
            { "NVM_READ", &RaidDevCommInTask::nvmRead },
            { "NVM_WRITE",  &RaidDevCommInTask::nvmWrite },
            { "NVM_PROVISION", &RaidDevCommInTask::nvmProvision},
            { "NVM_STATUS", &RaidDevCommInTask::nvmStatus }
        };

        // call the found member function:        
        (this->*command_table.at(cmd))(msg, resp_frame);
    }

private:
    void nvmRead(unsigned char*, unsigned char*){}
    void nvmWrite(unsigned char*, unsigned char*){}
    void nvmProvision(unsigned char*, unsigned char*){}
    void nvmStatus(unsigned char*, unsigned char*){}
};

If you need the map accessible from other functions, make it static to the class instead of inside call.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0

This seems to be working fine for me.

std::map<std::string, void (RaidDevCommInTask::*)(unsigned char* msg, unsigned char* resp_frame)> command_table = {
    { "NVM_READ", &RaidDevCommInTask::nvmRead },
    { "NVM_WRITE", &RaidDevCommInTask::nvmWrite },
    { "NVM_PROVISION", &RaidDevCommInTask::nvmProvision },
    { "NVM_STATUS", &RaidDevCommInTask::nvmStatus }
  };

I then call it using:

(this->*command_table.at("NVM_READ"))(parm1, parm2);
chriskot
  • 21
  • 2