c++templatesmember-functions

Use template to select which templatized member function is called


In my real code I am parsing a series of bytes that are consecutive messages. An integer in a message header determines what is the appropriate way to interpret the bytes

To simplify for SO assume the following:

Also for SO assume that a class, NamePrinter is responsible for:

I know how to write code for this. I also know how to switch out NamePrinter with another class that also performs actions on messages. What I do not do not know how to do is chose which member function of NamePrinter (or another class) to call in a more general case

#include <iostream>

// Some made up "message" structs
struct MsgA final {
    static constexpr int getId() { return 1; }
    const char* getName() const { return "MsgA"; }
    const char* getSomethingElse() const { return "SomethingElseA"; }
};
struct MsgB final {
    static constexpr int getId() { return 2; }
    const char* getName() const { return "MsgB"; }
    const char* getSomethingElse() const { return "SomethingElseB"; }
};
struct MsgC final {
    static constexpr int getId() { return 3; }
    const char* getName() const { return "MsgC"; }
    const char* getSomethingElse() const { return "SomethingElseC"; }
};

// A class that creates a "message" and performs and action on it
class NamePrinter final {
public:
    template <class T>
    void onMsgPrintName() const {
        T t;
        std::cout << t.getName() << std::endl;
    }

    template <class T>
    void onMsgPrintSomethingElse() const {
        T t;
        std::cout << t.getSomethingElse() << std::endl;
    }
};

// A function that can convert an integer into a "message" at compile time but
// is hardcoded to call onMsgPrintName
template <class T>
void intToPrintName(const T& a_caller, const int a_id) {
    switch (a_id) {
    case MsgA::getId(): a_caller.template onMsgPrintName<MsgA>(); break;
    case MsgB::getId(): a_caller.template onMsgPrintName<MsgB>(); break;
    case MsgC::getId(): a_caller.template onMsgPrintName<MsgC>(); break;
    default:
        std::cout << "Runtime error" << std::endl;
    }
}

// This is invalid but tries to convey that onMsgPrintName has been replaced
// with a template
//template <class F, class T>
//void intToFunc(const T& a_caller, const int a_id) {
//    switch (a_id) {
//    case MsgA::getId(): a_caller.template F<MsgA>(); break;
//    case MsgB::getId(): a_caller.template F<MsgB>(); break;
//    case MsgC::getId(): a_caller.template F<MsgC>(); break;
//    default:
//        std::cout << "Runtime error" << std::endl;
//    }
//}

int main(int argc, char** argv) {
    const NamePrinter np;

    // This will end up calling NamePrinter::onMsgPrintName
    intToPrintName(np, argc);

    // I want something like this that allows me to pick what is called
    //intToFunc<NamePrinter::onMsgPrintName         >(np, argc);
    //intToFunc<NamePrinter::onMsgPrintSomethingElse>(np, argc);

    return 0;
}

What I would like to know is if I can replace onMsgPrintName inside of intToPrintName with an arbitrary member function of my choice. The commented out code shows an attempt to do this which does not compile


Solution

  • Add another level of indirection - a wrapper that knows how to call a particular method on any object. Along the lines of

    class OnMsgPrintNameCaller {
      template <typename Param, typename Obj, typename... Args>
      static auto Call(Obj&& obj, Args&&... args) {
        return std::forward<Obj>(obj).template onMsgPrintName<Param>(
          std::forward<Args>(args)...);
      }
    };
    

    And similar class for onMsgPrintSomethingElse. Now pass this as an extra parameter to intToPrintName:

    template <typename MethodCaller, typename T>
    void intToPrintName(const T& a_caller, const int a_id) {
        switch (a_id) {
        case MsgA::getId(): MethodCaller::template Call<MsgA>(a_caller); break;
        // ...
        }
    }
    

    Usage:

    intToPrintName<OnMsgPrintNameCaller>(np, argc);