如果可用,请使用模板参数的实现,否则为默认值

Use template argument's implementation if available, default otherwise

本文关键字:实现 默认值 请使用 如果 参数      更新时间:2023-10-16

我想为不同的Controller创建一个Frontend模板类。如果方法可用(即支持某个功能),Frontend应该使用Controller的方法实现,否则应该使用默认方法,如

template <typename Controller>
class Frontend
{
  public:
    something()
    {
      // use Controller::something() if possible
      // else use default implementation
    }
};
  • CCD_ 5将在内部使用类型特征来了解更多关于CCD_
  • Controller不需要从任何提供默认实现的基类派生,因为默认方法实现将需要Frontend专用的信息

将有大约20种方法可以在Controller中实现。我试图创建(嵌套的)特征,提供有关所提供实现的信息:

// (T: Controller class)
T::supportsFeature<FeatureClass, ...>::type

因此,必须提供Controller::supportsFeature<>来通知具体的实现。如果控制器支持某个功能,则::typestd::true_type,如果不支持,则为std::false_type。为此,我创建了一个默认结构,该结构禁用任何功能,因此Controller类必须显式启用它提供的任何功能。这似乎是一种将信息从Controller传送到Frontend的舒适方式,但它有两个主要缺点:

  1. 其他人(最终将提供Controller)很难实现supportsFeature,因为嵌套traits类的专门化必须编写

  2. 我不确定我应该如何回避Frontend::something()中某个特征的存在,因为没有任何论点取决于该特征的存在——我无法提供一个合理表达的重载(我不想重载std::true_typestd::false_type,因为这本身就不说明问题)。我可以简单地使用if语句,并依靠编译器来删除死代码。我应该吗?

所以,总结一下:

  • 如何将有关方法存在的信息从模板类参数传递到模板类
  • 如何根据这些信息在实现之间正确切换

我用它来检查确切的签名:

#include <cstdint>
#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               
    template <typename U>                                                   
    class traitsName                                                        
    {                                                                       
    private:                                                                
        template<typename T, T> struct helper;                              
        template<typename T>                                                
        static std::uint8_t check(helper<signature, &funcName>*);           
        template<typename T> static std::uint16_t check(...);               
    public:                                                                 
        static                                                              
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); 
    }
DEFINE_HAS_SIGNATURE(has_something, T::something, void (T::*)());

然后使用SFINAE(类似):

template <typename Controller>
class Frontend
{
public:
    void something()
    {
        somethingT<Controller>();
    }
private:
    template <typename T>
    typename std::enable_if<has_something<T>::value>::type
    somethingT()
    {
        controller.something();
    }
    template <typename T>
    typename std::enable_if<!has_something<T>::value>::type
    somethingT()
    {
        // default implementation
    }
};

或标签调度(类似):

template <typename Controller>
class Frontend
{
public:
    void something()
    {
        something(typename std::conditional<has_something<Controller>::value,
            std::true_type,
            std::false_type>::type());
    }
private:
    void something(std::true_type) { controller.something(); }
    void something(std::false_type) { /* default implementation */ }
};

经过一些尝试/错误后,以下是我所称的可接受的解决方案。

当然,使用SFINAE,关于是使用Controller的成员函数还是默认函数的所有推导都是在编译时完成的。

Controllers类型的实现者唯一需要的就是将控制器中的类型定义为typedef Controller WithSomething;。与你提到的特质相比,这并不难。


首先声明Frontend模板类,并为20个可调用函数中的每一个定义两个模板函数。这里只有两个foobar

#include <iostream>
using std::cout;
template <typename Ctrl>
class Frontend;
template <typename Ctrl>
void call_foo( typename Ctrl::WithFoo & ctrl ) { ctrl.foo(); }
template <typename Ctrl>
void call_foo( Ctrl & ctrl ) { Frontend<Ctrl>::default_foo( ctrl ); }
template <typename Ctrl>
void call_bar( typename Ctrl::WithBar & ctrl ) { ctrl.bar(); }
template <typename Ctrl>
void call_bar( Ctrl & ctrl ) { Frontend<Ctrl>::default_bar( ctrl ); }

然后用可调用函数定义Frontend函数。在这里,我将默认实现定义为静态成员,但这是可以更改的。

template <typename Ctrl>
class Frontend
{
public:
    typedef Ctrl controller;
    void foo() { call_foo<Ctrl>( c ); }
    void bar() { call_bar<Ctrl>( c ); }
    static void default_foo( Ctrl & ctrl ) { cout<<"Default foon"; }
    static void default_bar( Ctrl & ctrl ) { cout<<"Default barn"; }
private:
    Ctrl c;
};

最后给出了Controller类的一些例子。一个同时定义foobar,另两个仅定义一个。

struct CtrlFooBar
{
    typedef CtrlFooBar WithFoo;
    typedef CtrlFooBar WithBar;
    void foo() { cout<<"CtrlFB  foon"; }
    void bar() { cout<<"CtrlFB  barn"; }
};
struct CtrlFoo
{
    typedef CtrlFoo WithFoo;
    void foo() { cout<<"CtrlFoo foon"; }
};
struct CtrlBar
{
    typedef CtrlBar WithBar;
    void bar() { cout<<"CtrlBar barn"; }
};

Frondtend与所有这些类一起使用,并将int与预期一起使用。

int main()
{
    Frontend<CtrlFooBar> c2;
    Frontend<CtrlFoo>    cf;
    Frontend<CtrlBar>    cb;
    Frontend<int>        ci;
    c2.foo();
    c2.bar();
    cf.foo();
    cf.bar();
    cb.foo();
    cb.bar();
    ci.foo();
    ci.bar();
    return 0;
}

输出

CtrlFB  foo
CtrlFB  bar
CtrlFoo foo
Default bar
Default foo
CtrlBar bar
Default foo
Default bar