在C++中实现具有成员函数指针作为值的映射模板

huangapple go评论76阅读模式
英文:

Implementing a Map Template with Member Function Pointers as Values in C++

问题

我实现了一个名为MethodMap的类,允许我存储类的成员函数指针,并使用字符串键在运行时调用它们。成员函数可以接受任何参数或者不接受参数。该类的示例代码如下:

template <typename T, typename... Args>
class MethodMap {
private:
    std::unordered_map<std::string, std::function<void(T*, Args...)>> method_map;

public:
    void Insert(const std::string& key, void (T::* method)(Args...)) {
        method_map[key] = [method](T* obj, Args... args) { (obj->*method)(args...); };
    }

    void Call(const std::string& key, T* instance, Args&&... methodArgs) const {
        auto it = method_map.find(key);
        if (it != method_map.end()) {
            auto& func = it->second;
            // 使用元组来存储和传递参数
            std::tuple<Args...> arg_tuple(std::forward<Args>(methodArgs)...);
            std::apply(func, std::tuple_cat(std::make_tuple(instance), arg_tuple));
            return;
        }
        std::cerr << "Error: method '" << key << "' not found" << std::endl;
    }
};

Insert 方法将成员函数指针插入映射,而 Call 方法使用给定的键和参数调用成员函数。

它的工作方式很好,但我意识到需要为每个接受不同参数的成员函数指针创建一个不同的 MethodMap 实例。例如,如果我有以下成员函数:

class MyClass {
public:
    void Method1(int x);
    void Method2(double d);
    void Method3(int x, const std::string& s);
    void Method4();
};

因为它们具有不同的参数列表,我需要为每个成员函数指针创建一个不同的 MethodMap 实例。例如:

MethodMap<MyClass> methodmap;
MyClass myClass;
methodmap.Insert("key", &MyClass::Method4);
methodmap.Call("key", &myClass);
MethodMap<MyClass, int> methodmapWithParameters;
methodmapWithParameters.Insert("key", &MyClass::Method1);
methodmapWithParameters.Call("key", &myClass, 1);

有没有办法使用单个 MethodMap 实例来处理这种情况?我遇到了类似的问题,但是所有这些问题中给定的参数始终是相同的,我很难自己推广到不同参数的情况。

英文:

I have implemented a class called MethodMap that allows me to store member function pointers of a class and call them at runtime using a key string. The member function can take any parameters or not at all. The class looks like this:

template &lt;typename T, typename... Args&gt;
class MethodMap {
private:
	std::unordered_map&lt;std::string, std::function&lt;void(T*, Args...)&gt;&gt; method_map;
public:
	void Insert(const std::string&amp; key, void (T::* method)(Args...)) {
		method_map[key] = [method](T* obj, Args... args) { (obj-&gt;*method)(args...); };
	}

	void Call(const std::string&amp; key, T* instance, Args&amp;&amp;... methodArgs) const {
		auto it = method_map.find(key);
		if (it != method_map.end()) {
			auto&amp; func = it-&gt;second;
			// use tuple to store and forward the arguments
			std::tuple&lt;Args...&gt; arg_tuple(std::forward&lt;Args&gt;(methodArgs)...);
			std::apply(func, std::tuple_cat(std::make_tuple(instance), arg_tuple));
			return;
		}
		std::cerr &lt;&lt; &quot;Error: method &#39;&quot; &lt;&lt; key &lt;&lt; &quot;&#39; not found&quot; &lt;&lt; std::endl;
	}
};

The Insert method inserts a member function pointer to the map, and the Call method calls the member function with the given key and arguments.

It works well, but I realized that I need to create a different instance of MethodMap for every member function pointer that takes different arguments. For example, if I have the following member functions:

class MyClass {
public:
    void Method1(int x);
    void Method2(double d);
    void Method3(int x, const std::string&amp; s);
    void Method4();
};

I would need to create a different instance of MethodMap for each member function pointer because they have different argument lists. For example:

MethodMap&lt;MyClass&gt; methodmap;
MyClass myClass;
methodmap.Insert(&quot;key&quot;, &amp;MyClass::Method4); 
methodmap.Call(&quot;key&quot;, &amp;myClass); 
MethodMap&lt;MyClass, int&gt; methodmapWithParameters; 
methodmapWithParameters.Insert(&quot;key&quot;, &amp;MyClass::Method1);
methodmapWithParameters.Call(&quot;key&quot;, &amp;myClass, 1);

Is there a way to handle this with a single instance of MethodMap?
I did encounter similar questions, but in all of them the parameters given were always the same and I'm having trouble to generalize this myself.

答案1

得分: 2

以下是翻译好的代码部分:

作为另一个答案使用了`dynamic_cast`,而我更倾向于避免使用它,所以我向您展示一个不使用它的替代方法。思路是在一个映射获取模板成员函数中使用`static`,这将`this`映射到`name`和`function`。然后,您的成员函数将具有模板参数,而不是您的`class`:

```cpp
#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>

class MethodMap {
private:
public:
    template <typename T, typename... Args>
    std::unordered_map<std::string, std::function<void(T*, Args...)>>& get_methodmap() const
    {
        static std::unordered_map<const MethodMap*, std::unordered_map<std::string, std::function<void(T*, Args...)>> this2name2method;
        return this2name2method[this];
    }

    template <typename T, typename... Args>
    void Insert(const std::string& key, void (T::* method)(Args...)) {
        get_methodmap<T, Args...>()[key] = [method](T* obj, Args... args) { (obj->*method)(args...); };
    }

    template <typename T, typename... Args>
    void Call(const std::string& key, T* instance, Args&&... methodArgs) const {
        auto&& method_map = get_methodmap<T, Args...>();
        auto it = method_map.find(key);
        if (it != method_map.end()) {
            auto& func = it->second;
            // 使用元组来存储和转发参数
            std::tuple<Args...> arg_tuple(std::forward<Args>(methodArgs)...);
            std::apply(func, std::tuple_cat(std::make_tuple(instance), arg_tuple));
            return;
        }
        std::cerr << "错误:未找到方法 '" << key << "'" << std::endl;
    }
};

class MyClass {
public:
    void Method1(int x) {}
    void Method2(double d) {}
    void Method3(int x, const std::string& s) {}
    void Method4() {}
};

int main()
{
    MethodMap methodmap;
    MyClass myClass;
    methodmap.Insert("key", &MyClass::Method4); 
    methodmap.Call("key", &myClass); 
    methodmap.Insert("key", &MyClass::Method1);
    methodmap.Call("key", &myClass, 1);
}

请注意,原始代码中使用了HTML实体,我已经将它们替换为普通字符以便进行翻译。
<details>
<summary>英文:</summary>
As the other answer used `dynamic_cast`, which I prefer to avoid, I&#39;m showing you an alternative without it. Idea is to have a `static` in a map getter template member function; this maps `this` to `name` to `function`. Then your member functions will have template arguments instead of your `class`:

#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>

class MethodMap {
private:
public:
template <typename T, typename... Args>
std::unordered_map<std::string, std::function<void(T*, Args...)>>& get_methodmap() const
{
static std::unordered_map<const MethodMap*, std::unordered_map<std::string, std::function<void(T*, Args...)>>> this2name2method;
return this2name2method[this];
}

template &lt;typename T, typename... Args&gt;
void Insert(const std::string&amp; key, void (T::* method)(Args...)) {
get_methodmap&lt;T, Args...&gt;()[key] = [method](T* obj, Args... args) { (obj-&gt;*method)(args...); };
}
template &lt;typename T, typename... Args&gt;
void Call(const std::string&amp; key, T* instance, Args&amp;&amp;... methodArgs) const {
auto&amp;&amp; method_map = get_methodmap&lt;T, Args...&gt;();
auto it = method_map.find(key);
if (it != method_map.end()) {
auto&amp; func = it-&gt;second;
// use tuple to store and forward the arguments
std::tuple&lt;Args...&gt; arg_tuple(std::forward&lt;Args&gt;(methodArgs)...);
std::apply(func, std::tuple_cat(std::make_tuple(instance), arg_tuple));
return;
}
std::cerr &lt;&lt; &quot;Error: method &#39;&quot; &lt;&lt; key &lt;&lt; &quot;&#39; not found&quot; &lt;&lt; std::endl;
}

};

class MyClass {
public:
void Method1(int x) {}
void Method2(double d) {}
void Method3(int x, const std::string& s) {}
void Method4() {}
};

int main()
{
MethodMap methodmap;
MyClass myClass;
methodmap.Insert("key", &MyClass::Method4);
methodmap.Call("key", &myClass);
methodmap.Insert("key", &MyClass::Method1);
methodmap.Call("key", &myClass, 1);
}


</details>
# 答案2
**得分**: 1
以下是您要翻译的代码部分:
```cpp
What you could do is to have a class `member_base_ptr` that is virtual and that one can store in your map as a pointer (ideally some kind of managed pointer so that it is properly released), from that you can extend a `member_ptr` with the types you need. In that you do the look up for that `member_base_ptr` and try to do a `dynamic_cast` to that `member_ptr`, and if it is successful, you than can forward the call to that one.
Here a rough draft of that idea, but I didn't spend much time thinking about everything in that code, please verify if everything is really valid and does not result in undefined behavior.
#include <iostream>
#include <functional>
#include <memory>
#include <map>
struct Test {
int test1(float i){
std::cout << "test1" << "\n";
return 10;
}
int test2(std::string s){
std::cout << "test1" << "\n";
return 20;
}
};
struct member_base_ptr {
virtual ~member_base_ptr() = default;
};
template <typename T, typename RT, typename... Args>
struct member_ptr: public member_base_ptr {
std::function<RT(T*, Args...)> m_ptr;
member_ptr(RT (T::* method)(Args...)) {
m_ptr = [method](T* obj, Args... args) { return (obj->*method)(args...); };
}
RT call(T* instance, Args&&... methodArgs) const {
return m_ptr(instance, std::forward<Args>(methodArgs)...);
}
};
struct method_map {
std::map<std::string, std::unique_ptr<member_base_ptr>> m_ptrs;
void insert(std::string key, auto type) {
std::unique_ptr<member_base_ptr> ptr = std::make_unique<decltype(member_ptr(type))>(type);
m_ptrs.insert(std::make_pair(key, std::move(ptr));
}
template <typename RT, typename T, typename... Args>
RT call(const std::string& key, T* instance, Args&&... methodArgs) const {
auto it = m_ptrs.find(key);
if(it != m_ptrs.end()) {
member_base_ptr *base_ptr = it->second.get();
auto test = dynamic_cast<member_ptr<T, RT, Args...> *>(base_ptr);
if( test == nullptr ) {
throw std::runtime_error("casting failed");
}
return test->call(instance, std::forward<Args>(methodArgs)...);
}
throw std::runtime_error("not found");
}
};
int main()
{
Test t;
method_map map;
map.insert("test1", &Test::test1);
map.insert("test2", &Test::test2);
std::cout << map.call<int>("test1", &t, 1.f) << "\n";
std::cout << map.call<int>("test2", &t, std::string("test")) << "\n";
return 0;
}

这是您提供的C++代码的翻译。

英文:

What you could do is to have a class member_base_ptr that is virtual and that one can store in your map as a pointer (ideally some kind of managed pointer so that it is properly released), from that you can extend a member_ptr with the types you need. In that you do the the look up for that member_base_ptr and try to do a dynamic_cast to that member_ptr, and if it is successful, you than can forward the call to that one.

Here a rough draft of that idea, but I didn't spend much time thinking about everything in that code, please verify if everything is really valid and does not result in undefined behavior.

#include &lt;iostream&gt;
#include &lt;functional&gt;
#include &lt;memory&gt;
#include &lt;map&gt;
struct Test {
int test1(float i){
std::cout &lt;&lt; &quot;test1&quot; &lt;&lt; &quot;\n&quot;;
return 10;
}
int test2(std::string s){
std::cout &lt;&lt; &quot;test1&quot; &lt;&lt; &quot;\n&quot;;
return 20;
}
};
struct member_base_ptr {
virtual ~member_base_ptr() = default;
};
template &lt;typename T, typename RT, typename... Args&gt;
struct member_ptr: public member_base_ptr {
std::function&lt;RT(T*, Args...)&gt; m_ptr;
member_ptr(RT (T::* method)(Args...)) {
m_ptr = [method](T* obj, Args... args) { return (obj-&gt;*method)(args...); };
}
RT call(T* instance, Args&amp;&amp;... methodArgs) const {
return m_ptr(instance, std::forward&lt;Args&gt;(methodArgs)...);
}
};
struct method_map {
std::map&lt;std::string, std::unique_ptr&lt;member_base_ptr&gt;&gt; m_ptrs;
void insert(std::string key, auto type) {
std::unique_ptr&lt;member_base_ptr&gt; ptr = std::make_unique&lt;decltype(member_ptr(type))&gt;(type);
m_ptrs.insert(std::make_pair(key, std::move(ptr)));
}
template &lt;typename RT, typename T, typename... Args&gt;
RT call(const std::string&amp; key, T* instance, Args&amp;&amp;... methodArgs) const {
auto it = m_ptrs.find(key);
if(it != m_ptrs.end()) {
member_base_ptr *base_ptr = it-&gt;second.get();
auto test = dynamic_cast&lt;member_ptr&lt;T, RT, Args...&gt; *&gt;(base_ptr);
if( test == nullptr ) {
throw std::runtime_error(&quot;casting failed&quot;);
}
return test-&gt;call(instance, std::forward&lt;Args&gt;(methodArgs)...);
}
throw std::runtime_error(&quot;not found&quot;);
}
};
int main()
{
Test t;
method_map map;
map.insert(&quot;test1&quot;, &amp;Test::test1);
map.insert(&quot;test2&quot;, &amp;Test::test2);
std::cout &lt;&lt; map.call&lt;int&gt;(&quot;test1&quot;, &amp;t, 1.f) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; map.call&lt;int&gt;(&quot;test2&quot;, &amp;t, std::string(&quot;test&quot;)) &lt;&lt; &quot;\n&quot;;
return 0;
}

Here a changed version of the code that allows "type hinting" for the insert function if overloaded functions should be supported:

#include &lt;iostream&gt;
#include &lt;functional&gt;
#include &lt;memory&gt;
#include &lt;map&gt;
struct Test {
int test1(float i){
std::cout &lt;&lt; &quot;test1 f&quot; &lt;&lt; &quot;\n&quot;;
return 10;
}
int test1(int i){
std::cout &lt;&lt; &quot;test1 i&quot; &lt;&lt; &quot;\n&quot;;
return 10;
}
int test2(std::string s){
std::cout &lt;&lt; &quot;test1&quot; &lt;&lt; &quot;\n&quot;;
return 20;
}
};
struct member_base_ptr {
virtual ~member_base_ptr() = default;
};
template &lt;typename T, typename RT, typename... Args&gt;
struct member_ptr: public member_base_ptr {
std::function&lt;RT(T*, Args...)&gt; m_ptr;
member_ptr(RT (T::* method)(Args...)) {
m_ptr = [method](T* obj, Args... args) { return (obj-&gt;*method)(args...); };
}
RT call(T* instance, Args&amp;&amp;... methodArgs) const {
return m_ptr(instance, std::forward&lt;Args&gt;(methodArgs)...);
}
};
struct method_map {
std::map&lt;std::string, std::unique_ptr&lt;member_base_ptr&gt;&gt; m_ptrs;
template &lt;typename... Args, typename RT, typename T&gt;
void insert(std::string key,RT (T::* method)(Args...)) {
std::unique_ptr&lt;member_base_ptr&gt; ptr = std::make_unique&lt;member_ptr&lt;T, RT, Args ...&gt;&gt;(method);
m_ptrs.insert(std::make_pair(key, std::move(ptr)));
}
template &lt;typename RT, typename T, typename... Args&gt;
RT call(const std::string&amp; key, T* instance, Args&amp;&amp;... methodArgs) const {
auto it = m_ptrs.find(key);
if(it != m_ptrs.end()) {
member_base_ptr *base_ptr = it-&gt;second.get();
auto test = dynamic_cast&lt;member_ptr&lt;T, RT, Args...&gt; *&gt;(base_ptr);
if( test == nullptr ) {
throw std::runtime_error(&quot;casting failed&quot;);
}
return test-&gt;call(instance, std::forward&lt;Args&gt;(methodArgs)...);
}
throw std::runtime_error(&quot;not found&quot;);
}
};
int main()
{
Test t;
method_map map;
map.insert&lt;float&gt;(&quot;test1f&quot;, &amp;Test::test1);
map.insert&lt;int&gt;(&quot;test1i&quot;, &amp;Test::test1);
map.insert(&quot;test2&quot;, &amp;Test::test2);
std::cout &lt;&lt; map.call&lt;int&gt;(&quot;test1f&quot;, &amp;t, 1.f) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; map.call&lt;int&gt;(&quot;test1i&quot;, &amp;t, 1) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; map.call&lt;int&gt;(&quot;test2&quot;, &amp;t, std::string(&quot;test&quot;)) &lt;&lt; &quot;\n&quot;;
return 0;
}

答案3

得分: 0

我已经完成代码部分的翻译,以下是翻译好的代码部分:

我选择了一个没有虚拟调度并将具有不同签名的函数存储在单独的映射中的版本。实例化 `MethodMap` 将通过指定映射应该支持的成员函数签名来完成。示例:

class MyClass {
public:
    void Method1(int x) { std::cout << "got int " << x << '\n'; }
    double Method2(double a, double b) { return a + b; }
    int Method3(int x) { return x * x; }
};

MethodMap<MyClass, void(int), double(double, double), int(int)> methodmap;

然后内部的 "map" 成为一个 `std::tuple`:

template<class...> struct arg_pack;
template<class T, class R, class... Args> struct arg_pack<T, R(Args...)> {
    using function_type = std::function<R(T&, Args...)>;
};
template<class... Ts> using arg_pack_t = typename arg_pack<Ts...>::function_type;

template <class T, class... ArgPacks>
class MethodMap {
public:
    using map_type = std::tuple<std::unordered_map<std::string, arg_pack_t<T, ArgPacks>>...>;
private:
    map_type method_map;
};

存储新的成员函数指针首先通过从 `tuple`  `std::get` 出正确的映射来完成。这个 "查找" 是在编译时完成的:

template<class R, class... Args>
void Insert(const std::string& key, R(T::*method)(Args...) ) {
    auto& m = std::get<std::unordered_map<std::string, arg_pack_t<T, R(Args...)>>(method_map);

    m[key] = [method](T& instance, Args&&... args) -> decltype(auto) {
        return (instance.*method)(std::forward<Args>(args)...);
    };
}

调用函数也以类似的方式完成:

template<class R, class... Args>
decltype(auto) Call(const std::string& key, T& instance, Args&&... args) const {
    auto& m = std::get<std::unordered_map<std::string, arg_pack_t<T, R(Args...)>>(method_map);
    return m.at(key)(instance, std::forward<Args>(args)...);
}

由于调用这些函数还支持除 `void` 之外的返回值,您需要向 `Call` 提供返回类型:

int main() {
    MethodMap<MyClass, void(int), double(double, double), int(int)> methodmap;
    MyClass myClass;

    methodmap.Insert("key1", &MyClass::Method1);
    methodmap.Insert("key2", &MyClass::Method2);
    methodmap.Insert("key3", &MyClass::Method3);

    methodmap.Call<void>("key1", myClass, 1); // void

    std::cout << methodmap.Call<double>("key2", myClass, 2.141, 1.0) << '\n';
    std::cout << methodmap.Call<int>("key3", myClass, 5) << '\n';
}

演示

英文:

I've opted for a version without virtual dispatch and store functions with different signatures in separate maps instead. Instantiating a MethodMap will then be done by specifying what member function signatures the map should support. Example:

class MyClass {
public:
    void Method1(int x) { std::cout &lt;&lt; &quot;got int &quot; &lt;&lt; x &lt;&lt; &#39;\n&#39;; }
    double Method2(double a, double b) { return a + b; }
    int Method3(int x) { return x * x; }
};

MethodMap&lt;MyClass, void(int), double(double, double), int(int)&gt; methodmap;

The inner "map" then becomes a std::tuple:

template&lt;class...&gt; struct arg_pack;
template&lt;class T, class R, class... Args&gt; struct arg_pack&lt;T, R(Args...)&gt; {
    using function_type = std::function&lt;R(T&amp;, Args...)&gt;;
};
template&lt;class... Ts&gt; using arg_pack_t = typename arg_pack&lt;Ts...&gt;::function_type;

template &lt;class T, class... ArgPacks&gt;
class MethodMap {
public:
    using map_type = std::tuple&lt;std::unordered_map&lt;std::string,
                                                   arg_pack_t&lt;T, ArgPacks&gt;&gt;...&gt;;
private:
    map_type method_map;
};

Storing new member function pointers is done by first std::getting the correct map from the tuple. This "lookup" is done at compile time:

template&lt;class R, class... Args&gt;
void Insert(const std::string&amp; key, R(T::*method)(Args...) ) {
    auto&amp; m = std::get&lt;std::unordered_map&lt;std::string,
                                          arg_pack_t&lt;T, R(Args...)&gt;&gt;&gt;(method_map);

    m[key] = [method](T&amp; instance, Args&amp;&amp;... args) -&gt; decltype(auto) {
        return (instance.*method)(std::forward&lt;Args&gt;(args)...);
    };
}

and calling functions is done in a similar fashion:

template&lt;class R, class... Args&gt;
decltype(auto) Call(const std::string&amp; key, T&amp; instance, Args&amp;&amp;... args) const {
    auto&amp; m = std::get&lt;std::unordered_map&lt;std::string,
                                          arg_pack_t&lt;T, R(Args...)&gt;&gt;&gt;(method_map);
    return m.at(key)(instance, std::forward&lt;Args&gt;(args)...);
}

Since calling these functions also supports return values other than void, you supply the return type to Call:

int main() {
    MethodMap&lt;MyClass, void(int), double(double, double), int(int)&gt; methodmap;
    MyClass myClass;

    methodmap.Insert(&quot;key1&quot;, &amp;MyClass::Method1);
    methodmap.Insert(&quot;key2&quot;, &amp;MyClass::Method2);
    methodmap.Insert(&quot;key3&quot;, &amp;MyClass::Method3);

    methodmap.Call&lt;void&gt;(&quot;key1&quot;, myClass, 1); // void

    std::cout &lt;&lt; methodmap.Call&lt;double&gt;(&quot;key2&quot;, myClass, 2.141, 1.0) &lt;&lt; &#39;\n&#39;;
    std::cout &lt;&lt; methodmap.Call&lt;int&gt;(&quot;key3&quot;, myClass, 5) &lt;&lt; &#39;\n&#39;;
}

Demo

huangapple
  • 本文由 发表于 2023年2月26日 20:29:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/75571983.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定