c++c++17stdany

correct way to store a pointer to member function inside std::any


Looking into this question about map of member function I'm observing an anomaly in the way to pass a pointer to member function into a std::any.

I'm using the following snippet:

#include <any>
#include <iostream>
#include <map>

class VarCall {
   public:
    VarCall()
        : calls({{"Alice", std::any(&VarCall::foo)},
                 {"Bob", std::any(&VarCall::bar)},
                 {"Charlie", std::any(&VarCall::baz)}}) {}

    template <typename... Args>
    void call(const std::string& what, Args... args) {
        void (VarCall::*ptr)(Args...);
        std::any a = ptr;
        std::cout << a.type().name() << std::endl;
        std::cout << calls[what].type().name() << std::endl;
        // failed attempt to call
        // this->*(std::any_cast<decltype(ptr)>(calls[what]))(args...);
    }

   public:
    void foo() { std::cout << "foo()" << std::endl; }
    void bar(const std::string& s) {
        std::cout << "bar(" << s << ")" << std::endl;
    }
    void baz(int i) { std::cout << "baz(" << i << ")" << std::endl; }

    std::map<std::string, std::any> calls;
};

int main() {
    VarCall v;

    void (VarCall::*ptr)(const std::string& s);
    std::any a = ptr;
    std::any b(&VarCall::bar);
    std::cout << a.type().name() << std::endl;
    std::cout << b.type().name() << std::endl;

    // v.call("Alice");
    v.call("Bob", "2");
    // v.call("Charlie", 1);

    return 0;
}

I'm expecting to have the following output:

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

but get

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

M7VarCallFvPKcE

M7VarCallFvRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

When initializing the std::any inside the VarCall() initializer list, I'm getting a different signature.

What is happening? Is there a way to get the expected output? The final goal, as indicated in the original question from @merula, if to be able to call the member function, for instance with: std::any_cast<std::add_pointer_t<void(Args ...)>>(calls[what])(args...); inside call member function.


Solution

  • With the help of @Mestkon, a more complete answer (also to this question) would be:

    #include <any>
    #include <iostream>
    #include <map>
    
    class VarCall {
       public:
        VarCall()
            : calls({{"Alice", std::any(&VarCall::foo)},
                     {"Bob", std::any(&VarCall::bar)},
                     {"Charlie", std::any(&VarCall::baz)}}) {}
    
        template <typename... Args>
        void call(const std::string& what, const Args&... args) {
            using ptr_t = void (VarCall::*)(const Args&...);
            // all parenthesis are important
            (this->*(std::any_cast<ptr_t>(calls[what])))(args...);
        }
    
       public:
        void foo() { std::cout << "foo()" << std::endl; }
        void bar(const std::string& s) {
            std::cout << "bar(" << s << ")" << std::endl;
        }
        void baz(int const& i) { std::cout << "baz(" << i << ")" << std::endl; }
    
        std::map<std::string, std::any> calls;
    };
    
    int main() {
        VarCall v;
    
        v.call("Alice");
        v.call("Bob", std::string("2"));
        v.call("Charlie", int(1));
    
        return 0;
    }
    

    Live

    [EDIT] in answer to @merula comment, and in order to illustrate my anwer to that comment, here is a code using forwarding-reference:

    #include <any>
    #include <iostream>
    #include <map>
    
    class VarCall {
       public:
        VarCall()
            : calls({
                  {"Alice", std::any(&VarCall::foo)},
                  {"Bob", std::any(&VarCall::bar)},
                  {"Charlie", std::any(&VarCall::baz)},
                  {"Bobrv", std::any(&VarCall::barrv)},
                  {"Charlierv", std::any(&VarCall::bazrv)},
              }) {}
    
        // using forwarding reference
        template <typename... Args>
        void call(const std::string& what, Args&&... args) {
            using ptr_t = void (VarCall::*)(Args&&...);
            (this->*(std::any_cast<ptr_t>(calls[what])))(
                std::forward<Args...>(args)...);
        }
    
       public:
        void foo() { std::cout << "foo()" << std::endl; }
        void bar(const std::string& s) {
            std::cout << "bar(" << s << ")" << std::endl;
        }
        void baz(int const& i) { std::cout << "baz(" << i << ")" << std::endl; }
        void barrv(std::string&& s) {
            std::cout << "barrv(" << s << ")" << std::endl;
        }
        void bazrv(int&& i) { std::cout << "bazrv(" << i << ")" << std::endl; }
    
        std::map<std::string, std::any> calls;
    };
    
    int main() {
        VarCall v;
    
        v.call("Alice");
        v.call("Bobrv", std::string("2"));
        // v.call("Bob", std::string("2"));    // KO
        v.call("Charlierv", int(1));
        // v.call("Charlie", int(1));  // KO
    
        return 0;
    }
    

    Live The main idea is that a rvalue of type T is also map to an rvalue of same type but the VarCall API did not contain functions with rvalue signature. Thus there must be added as in the snippet above.