HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Mapping a textual method name to a function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
methodfunctiontextualnamemapping

Problem

My program (a daemon hosting a Minecraft server) exposes a D-Bus interface to allow interaction with the server from other processes. To that end, and to avoid a chain of repetitive conditionals in the invocation handler, I'm using a map of Glib::ustring keys (the method invoked) to std::bind-created function objects (code abbreviated slightly; see end of question for link to full source):

std::unordered_map&)>> handlerMap;
std::once_flag handlerMapInitFlag;

Minecraftd::Minecraftd1() {
    // Only initialise the method name/handler method map once. Placeholders are used for the "this" and invocation arguments.
    std::call_once(handlerMapInitFlag, []{
        using namespace std::placeholders;
        handlerMap.emplace("SaveAll", std::bind(&Minecraftd1::handleSimpleCommand, _1, _2, "save-all"));
        // Omitted: two similar mappings
        handlerMap.emplace("Stop", std::bind(&Minecraftd1::handleSimpleCommand, _1, _2, "stop"));
    });
}

// Called by the Glib main loop when a D-Bus method call is received
void Minecraftd1::onMethodCall(const Glib::ustring &methodName, const Glib::RefPtr &invocation) {
    auto handler = handlerMap.find(methodName);
    if(handler != handlerMap.end()) {
        handler->second(this, invocation);
    } else {
        invocation->return_error(Gio::DBus::Error{Gio::DBus::Error::UNKNOWN_METHOD, "Method does not exist."});
    }
}

void Minecraftd1::handleSimpleCommand(const Glib::RefPtr &invocation, std::string command) const {
    // Write value of command to standard input (rebound to a pipe)
    invocation->return_value(Glib::VariantContainerBase{});
}


I realise that, for the current set of methods, I could just as well use a map of methodName to the command string to write to the file descriptor, but this way of doing it allows me to create more complex handlers for commands that require more than just a simple pass-through without having to change the execution flow of onMethodCall. Of course, if a handler

Solution

std::call_once is an interesting tool, but there are alternatives built into the language: namely, you can use the Immediately-Invoked Function Expression (IIFE) borrowed straight from JavaScript and made usable thanks to C++11 lambda functions.

Here is how you can use it to initialize handlerMap:

std::unordered_map&)>
> handlerMap = []{
    using namespace std::placeholders;
    std::unordered_map&)>
    > initMap;
    initMap.emplace("SaveAll", std::bind(&Minecraftd1::handleSimpleCommand, _1, _2, "save-all"));
    initMap.emplace("Stop", std::bind(&Minecraftd1::handleSimpleCommand, _1, _2, "stop"));
    return initMap;
}();


Agreed, it looks a bit messy with the big std::unordered_map type, but you could probably create a type alias to get rid of the syntactic mess. Even if you dont typedef the whole thing, I guess that exposing the type of std::function&)> could be an interesting idea.

Alterntively, there is even better than IIFE: in your can, you can use initializer lists to achieve what you're trying to do:

std::unordered_map&)>
> handlerMap = {
    { "SaveAll", std::bind(&Minecraftd1::handleSimpleCommand, std::placeholders::_1, std::placeholders::_2, "save-all") },
    { "Stop",    std::bind(&Minecraftd1::handleSimpleCommand, std::placeholders::_1, std::placeholders::_2, "stop") }
};


But it means that you have to explicitly write std::placeholders:: everywhere if you don't want using std::placeholders; to leak into the outer namespace. In other words, you have to choose which solutions seems to best convey your idea while not bearing too much syntactic noise.

Code Snippets

std::unordered_map<
    Glib::ustring,
    std::function<void(Minecraftd1*, const Glib::RefPtr<Gio::DBus::MethodInvocation>&)>
> handlerMap = []{
    using namespace std::placeholders;
    std::unordered_map<
        Glib::ustring,
        std::function<void(Minecraftd1*, const Glib::RefPtr<Gio::DBus::MethodInvocation>&)>
    > initMap;
    initMap.emplace("SaveAll", std::bind(&Minecraftd1::handleSimpleCommand, _1, _2, "save-all"));
    initMap.emplace("Stop", std::bind(&Minecraftd1::handleSimpleCommand, _1, _2, "stop"));
    return initMap;
}();
std::unordered_map<
    Glib::ustring,
    std::function<void(Minecraftd1*, const Glib::RefPtr<Gio::DBus::MethodInvocation>&)>
> handlerMap = {
    { "SaveAll", std::bind(&Minecraftd1::handleSimpleCommand, std::placeholders::_1, std::placeholders::_2, "save-all") },
    { "Stop",    std::bind(&Minecraftd1::handleSimpleCommand, std::placeholders::_1, std::placeholders::_2, "stop") }
};

Context

StackExchange Code Review Q#73966, answer score: 3

Revisions (0)

No revisions yet.