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.
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;
}
[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.