【问题标题】:C++ generic way to define multiple functions with a template使用模板定义多个函数的 C++ 通用方法
【发布时间】:2018-04-29 01:55:49
【问题描述】:

在 C++ 中是否可以根据提供的模板参数的数量定义多个方法?类似于可变参数函数的工作原理?

我可以做的功能

template <class ...Args>
struct VariadicFunctionCallback {
    typedef std::function<void(std::shared_ptr<Args>...)> variadic;
};

但我想知道的是,我是否可以做类似的事情,但要创建多个函数而不是多个参数

template <class ...FunctionArg>
class Example {
     void Function(FunctionArg)...
}

这将允许我做类似的事情

template <>
class Example<int, float> {
    void Function(int i) {
        ...
    }

    void Function(float f) {
        ...
    }
}

如果可能的话,与我目前的设置相比有什么优势

template<class EventType>
class EventHandler {
public:
    void HandleEvent(const std::shared_ptr<EventType>& event) {
    }
};

class ExampleEvent : public Event<ExampleEvent> {

};

class ExampleHandler : public EventHandler<ExampleHandler>, EventHandler<Events::ShutdownEvent> {
public:
    void HandleEvent(const std::shared_ptr<ExampleEvent> &event);

    void HandleEvent(const std::shared_ptr<Events::ShutdownEvent> &event);
};

--编辑-- 如果这两种解决方案混合在一起,我最终得到了混合。 它可能不是最好的,我会继续尝试并加班改进。

template <class EventType>
class BaseEventHandler {
public:
    EventIdentifier GetIdentifier() const {
        return EventType::GetIdentifier();
    }

    virtual void HandleEvent(const std::shared_ptr<EventType> &event) = 0;
};

template<class EventType, class ...EventTypes>
class EventHandler: public BaseEventHandler<EventTypes>... {

};

这让我可以这样做

class EventListener: public EventHandler<ShutdownEvent, MousePosEvent, WindowCloseRequestEvent> {
    void HandleEvent(const std::shared_ptr<ShutdownEvent> &event);
    void HandleEvent(const std::shared_ptr<MousePosEvent> &event);
    void HandleEvent(const std::shared_ptr<WindowCloseRequestEvent> &event);
}

【问题讨论】:

    标签: c++ templates variadic-templates c++17 template-specialization


    【解决方案1】:

    我想你可以让Example 成为一种递归的自继承类;像

        template <typename ...>
        struct Example
         {
           // dummy Function() to end the recursion
           void Function ()
            { }
         };
    
        template <typename T0, typename ... Ts>
        struct Example<T0, Ts...> : public Example<Ts...>
         {
           using Example<Ts...>::Function;
    
           void Function (T0 const &)
            { };
         };
    

    所以你可以写

    int main ()
     {
       Example<int, long, float>  e0;
    
       e0.Function(0);
       e0.Function(0L);
       e0.Function(0.0f);
     }
    

    -- 编辑--

    OP 询问

    然后可以在此基础上进行专业化吗?

    你的意思是如下吗?

    template <typename ...>
    struct Example
     {
       // dummy Function() to end the recursion
       void Function ()
        { }
     };
    
    template <typename T0, typename ... Ts>
    struct Example<T0, Ts...> : public Example<Ts...>
     {
       using Example<Ts...>::Function;
    
       void Function (T0 const &)
        { };
     };
    
    template <typename ... Ts>
    struct Example<float, Ts...> : public Example<Ts...>
     {
       void FunctionFloat (float const &)
        { };
     };
    
    int main ()
     {
       Example<int, long, float>  e0;
    
       e0.Function(0);
       e0.Function(0L);
       e0.FunctionFloat(0.0f);
       //e0.Function(0.0f); // compilation error
     }
    

    【讨论】:

    • 能否在此基础上进行专业化?
    • 即`template struct 示例:public Example { void Function (int i) {} void Function (long l) {} void Function (float f) {} } `
    • 需要using Example&lt;Ts...&gt;::Function; 吗?据我所知,公共基础函数可以在没有命名空间的情况下访问。
    • @Cethric - 是的;但我建议修改答案中的内容
    • @GillBates - 嗯...Example&lt;Ts...&gt;::Function 不完全是通过命名空间访问。是的:需要;尝试评论它,你会看到一些错误。您是对的:通常,无需使用即可访问公共基本功能。但是当你在派生类中定义一个方法时,你隐藏了从基类继承的同名方法;如果他们有不同的签名。因此,在这种情况下,您必须使用 using 来使继承的方法可见。
    【解决方案2】:

    This answer start's with max66's answer,或多或少

    我们从一个使用递归继承来实现我们的功能的类开始。在我的例子中,我选择了operator(),并使用了可变参数 using 声明将所有子 operator() 纳入范围:

    namespace detail{
        template<class T, class... U>
        struct ExampleImpl : ExampleImpl<U>...{
            using ExampleImpl<U>::operator()...;
            void operator()(T _arg){/*...*/}
        };
    }
    

    我的答案与 max66 的不同之处在于,我们将使用这个 ExampleImpl 类来撰写我们的 Example 类:

    template<class... T>
    class Example
    {
    public:
        template <class U>
        void Function(U arg)
        {
            impl(arg);
        }
        
        void Function(float arg) 
        {
            /*Your specialization code*/
        }
    private:
        detail::ExampleImpl<T...> impl;
    };
    

    我这样做有两个原因:

    1. 这种继承是一个实现细节,是您希望对客户隐藏的东西。
    2. *现在我们可以轻松地将Function 函数专门用于我们想要的任何类型,因为我们始终可以选择是否调用ExampleImpl 的实例。

    Demo

    如果ExampleImpl 需要使用Example 类的成员变量,那么您可以将ExampleImpl 转换为成熟的PIMPL 类,或者修改其构造函数或operator() 以接受附加参数作为Dependency Injection的一种形式

    *您可以轻松地执行完整的类特化,其中float 是特化中的模板参数之一,并定义您自己的Function。或者您可以使用tag dispatching 的形式隐藏float 版本,除非它在模板类型列表中。

    Tag dispatch demonstration

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-19
      • 1970-01-01
      • 1970-01-01
      • 2011-12-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多