【问题标题】:typedef on a templated function with member usagetypedef 在具有成员用法的模板化函数上
【发布时间】:2020-09-22 11:43:22
【问题描述】:

我有两个函数需要由类公开,它们看起来像这样(后面会更多):

void print_a(std::string s);
void print_b(std::string s, int val);

“在幕后”他们在做同样的事情,即在地图中查找并将调用参数传递给地图检索到的函数指针:

#include <stdint.h>
#include <iostream>
#include <string>
#include <map>

class Thing{
private:

    void do_a(){
        std::cout << "hello";
    }
    //there might be also a method do_a_extended() which has a different key in the map

    void do_b(int age){
        std::cout << "my age is " << age;
    }


    typedef void (Thing::*do_stuff_a)();
    typedef void (Thing::*do_stuff_b)(int);

    std::map<std::string, do_stuff_a> a_table;
    std::map<std::string, do_stuff_b> b_table;

public:
    
    void print_a(std::string s){
        do_stuff_a handler = a_table[s];
        if(handler){
            (this->*handler)();
        }
    }

    void print_b(std::string s, int val){
        do_stuff_b handler = b_table[s];
        if(handler){
            (this->*handler)(val);
        }
    }

};

我不喜欢涉及大量样板代码的事实。我想知道是否可以将成员传递到模板中以便我可以这样做:

class Thing{
private:

    void do_a(){
        std::cout << "hello";
    }

    void do_b(int age){
        std::cout << "my age is " << age;
    }


    typedef void (Thing::*do_stuff_a)();
    typedef void (Thing::*do_stuff_b)(int);

    std::map<std::string, do_stuff_a> a_table;
    std::map<std::string, do_stuff_b> b_table;


    template<<MAP_MEMBER>,typename ... PP>
    void print_x(std::string s, PP &&... pp){
        auto handler = <MAP_MEMBER>[s];
        if(handler){
            (this->*handler)(std::forward<PP>(pp) ...);
        }
    }
public:
    
    typedef decltype(print_x<a_table>) print_a;
    typedef decltype(print_x<b_table>) print_b;

};

感谢任何关于如何摆脱样板的想法。

【问题讨论】:

  • 您确定要将print_a 定义为decltype(print_x&lt;a_table&gt;) 的类型,而不是使用print_x 的函数吗?
  • 我不太确定我是否理解您的用例 - 函数是如何在地图/表格中注册的?是否只有为该类型注册的两个成员函数?
  • 基本上我在每张地图中有 6 个功能。所以 6 x do_stuff_a 和 6x do_stuff_b。我省略了地图的人口以提供一个尽可能简短的例子。
  • 而且都是成员函数?
  • 是的,他们必须是。

标签: c++ c++11 templates


【解决方案1】:

无需复杂,只需将您的打印机用作将成员传递给通用打印方法的包装器,如下所示:

class Foo
{
    int a;
    char b;
    
    template <typename M>
    void Print (M & member)
    {
        // complicated function 
    }
    
public:

    void PrintA ()
    {
        Print(a);   
    }

    void PrintB ()
    {
        Print(b);   
    }
};

因此,在您的示例中,公共打印函数成为包装函数:

class Thing
{
    // ...

    template <typename T, typename ... PP>
    void print (T & table, const std::string & key, PP && ... pp)
    {
        auto method = table[key];

        if (method)
            (this->*method)(std::forward<PP>(pp)...);
    }
    
public:

    template <typename ... PP>
    void print_a (PP && ... pp)
    {
        print(a_table, std::forward<PP>(pp)...);    
    }

    template <typename ... PP>
    void print_b (PP && ... pp)
    {
        print(b_table, std::forward<PP>(pp)...);    
    }
};

如果你使用-O3优化,这些公共方法应该被内联。

这是一个运行中的解决方案,样板更少,无需元编程:

#include <iostream>
#include <string>
#include <map>

class Thing
{
    void do_a_1 ()
    {
        std::cout << "hello" << std::endl;
    }
    
    void do_a_2 ()
    {
        std::cout << "goodbye" << std::endl;
    }

    void do_b (int age)
    {
        std::cout << "my age is " << age << std::endl;
    }
    
    template <typename ... PP>
    using MapMethod = std::map<std::string, void (Thing::*)(PP...)>;

    MapMethod<> a_table;
    MapMethod<int> b_table;
    
    template <typename T>
    void insert (T) {}
    
    template <typename T, typename M, typename ... PP>
    void insert (T & table, const std::string & key, M && method, PP && ... pp)
    {
        table.insert({key, method});
        insert(table, pp...);
    }
    
    template <typename T, typename ... PP>
    void print (const T & table, const std::string & key, PP && ... pp)
    {
        auto result = table.find(key);
        
        if (result != table.end())
        {
            auto method = result->second;
        
            (this->*method)(pp...);
        }
    }
    
public:

    Thing ()
    {
        insert(a_table,
            "apple", &Thing::do_a_1,
            "banana", &Thing::do_a_2);
            
        insert(b_table,
            "ostrich", &Thing::do_b);
    }

    void print_a (const std::string & key)
    {
        print(a_table, key);
    }
    
    void print_b (const std::string & key, int val)
    {
        print(b_table, key, val);
    }
};

int main ()
{
    Thing t;
    
    t.print_a("apple");
    t.print_b("ostrich", 12);
    t.print_a("banana");
    t.print_a("Do nothing");
}

如果您的包装方法在您的实际问题中不可避免地重复(也许完美的转发变得令人厌烦),您可以使用宏进一步减少样板以制作打印方法:

class Thing
{
    // private code is the same, except:
    // there's no need for the generic print method anymore
    
public:

    Thing ();

#define PRINT_MACRO(FUNCTION_NAME, MEMBER_TABLE) \
    template <typename ... PP> \
    void FUNCTION_NAME (const std::string & key, PP && ... pp) \
    { \
        auto result = MEMBER_TABLE.find(key); \
        if (result != MEMBER_TABLE.end()) \
        { \
            auto method = result->second; \
            (this->*method)(pp...); \
        } \
    }

    PRINT_MACRO(print_a, a_table)
    PRINT_MACRO(print_b, b_table)
#undef PRINT_MACRO
};

最后,您确定要使用std::map 而不是std::unordered_map?这个问题表明你不关心排序。

【讨论】:

    【解决方案2】:

    当您想要定义需要在标识符中连接的内容时,可以使用宏或代码生成。

    特别是,由于您还需要在生成的代码中处理任意数量的参数,所以我会使用代码生成。

    【讨论】:

      【解决方案3】:

      您可以将地图提取到一个额外的类中,该类也可以处理搜索和调用条目:

      
      template <typename C, typename K, typename...Args>
      class MemberFunctionCaller
      {
      private:
          using FunctionType = void(C::*)(Args...);
          using MapType = std::map<K, FunctionType>;
      
          MapType registry;
      
      
      public:
          void Register(const K& key, FunctionType value)
          {
              registry[key] = value;   
          }
      
          void Call(const K& key, C* self, const Args&...args)
          {
              auto iter = registry.find(key);
              if(iter != registry.end())
              {
                  FunctionType func = iter->second;
                  (self->*func)(args...);
              }
          }
      };
      

      一个简单的 typedef 可以简化“Thing”类中的使用:

          template <typename...Args>
          using ThingFunctionCaller = MemberFunctionCaller<Thing, std::string, Args...>;
      

      事物类可能看起来像这样:

      class Thing{
          template <typename...Args>
          using ThingFunctionCaller = MemberFunctionCaller<Thing, std::string, Args...>;
      
      private:
      
          void do_a(){
              std::cout << "hello" << std::endl;
          }
         
          void do_b(int age){
              std::cout << "my age is " << age << std::endl;
          }
      
          ThingFunctionCaller<> a_table;
          ThingFunctionCaller<int> b_table;
      
      public:
          void print_a(std::string s){
              a_table.Call(s, this);
          }
      
          void print_b(std::string s, int val){
              b_table.Call(s, this, val);
          }
      };
      

      这就是它在行动中的样子:https://gcc.godbolt.org/z/KM5a85

      【讨论】:

        【解决方案4】:

        您可以使用静态模板注册表来存储名称和成员函数之间的关系。 这还有其他缺点,但适用于您的特定用例。

        (通过一些额外的编码,您甚至可以使这项工作更“自然”)。

        template <typename C, typename...Args>
        using MemberFunctionPointer = void(C::*)(Args...);
        
        template <typename C, typename...Args>
        class MemberFunctionCaller
        {
        private:
            using FunctionType = MemberFunctionPointer<C, Args...>;
            using MapType = std::map<std::string, FunctionType>;
        
            static MapType& GetRegistry(){
                static MapType registry;
                return registry;
            }
        
        public:
            static void Register(const std::string& key, FunctionType function)
            {
                auto& registry = GetRegistry();
                registry[key] = function;   
            }
        
            static void Call(const std::string& key, C* self, const Args&...args)
            {
                auto& registry = GetRegistry();
                auto iter = registry.find(key);
                if(iter != registry.end())
                {
                    FunctionType func = iter->second;
                    (self->*func)(args...);
                }
            }
        };
        
        template <typename C>
        class MemberFunctionRegistry
        {
        public:
            template <typename...Args>
            static void Register(const std::string& key, MemberFunctionPointer<C, Args...> function)
            {
                MemberFunctionCaller<C, Args...>::Register(key, function);
            }
        
            template <typename...Args>
            static void Call(const std::string& key, C* self, const Args&...args)
            {
                MemberFunctionCaller<C, Args...>::Call(key, self, args...);
            }
        
        };
        

        您的 Thing 类可能如下所示:

        
        class Thing{
        
        private:
        
            void do_a(){
                std::cout << "hello" << std::endl;
            }
            
            void do_a_extended(){
                std::cout << "hello world" << std::endl;
            }
        
            void do_b(int age){
                std::cout << "my age is " << age << std::endl;
            }
        
            MemberFunctionRegistry<Thing> registry;
        public:
            
            static void RegisterMemberFunctions()
            {
                MemberFunctionRegistry<Thing>::Register("A", &Thing::do_a);
                MemberFunctionRegistry<Thing>::Register("AX", &Thing::do_a_extended);
                MemberFunctionRegistry<Thing>::Register("B", &Thing::do_b);
            }
        
            template <typename...Args>
            void print_x(std::string s, Args...args){
                registry.Call(s, this, args...);
            }
        };
        

        https://gcc.godbolt.org/z/fq5bez

        【讨论】:

        • 解决方案的简洁性看起来不错,但我无法更改类公开的方法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多