当模板的功能不同时C++模板

C++ template when the template's function is not the same

本文关键字:C++ 模板 功能      更新时间:2023-10-16

现在我有了一个模板类

template <class T>
class b
{
    void someFunc() {
        T t;
        t.setB();
    }
};

我知道模板T只会被实例化为2个类。

class D
{
public:
     void setB();
};
class R
{
public:
     void SetB();
};
可以看到,D类的函数名setB与R类的函数名setB不一样。所以在模板类b中,我不能只使用setB。如果我不能修改D或R,有什么方法吗?我可以在模板类中添加一些包装器或技巧来解决这个问题吗?

也许trait类可以帮助你:

struct Lower {};
struct Upper {};
// trait for most cases
template <typename T>
struct the_trait {
    typedef Lower Type;
};
// trait for special cases
template <>
struct the_trait<R> {
    typedef Upper Type;
};
template <class T>
class b {
public:
    void foo() {
        foo_dispatch(typename the_trait<T>::Type());
    }
private:
    void foo_dispatch(Lower) {
        T t;
        t.setB();
    }
    void foo_dispatch(Upper) {
        T t;
        t.SetB();
    }
};

正如@Arunmu所指出的,这种技术也被称为标签调度。

您可以为具有不同语义的类专门化您的模板:

template<>
class b<R>
{
    void doWork() {
        R obj;
        obj.SetB();
        // or R::SetB() if it was a static method.
    }
};

您还可以使用SFINAE检查函数是否存在,而不是使用自编程特征。

如果你想切换被调用的方法,每个类中必须有一个。如果检查发现多个测试方法,我提供的方法将不起作用!

下面的例子是为c++ 14编写的,但如果你用自己实现的函数替换新的库函数(当然不方便),也可以在c++03中使用

测试类has_Foo和has_Bar也可以嵌入到预处理器宏中,但我将其展开以使其更易于阅读。

它是如何工作的以及为什么需要更多的中间步骤在评论中解释了。看下面!

    #include <iostream>

    // First we write two classes as example. Both classes represents
    // external code which you could NOT modify, so you need an
    // adapter to use it from your code.
    class A
    {
        public:
            void Foo() { std::cout << "A::Foo" << std::endl; }
    };
    class B
    {
        public:
            void Bar() { std::cout << "B::Bar" << std::endl; }
    };
    // To benefit from SFINAE we need two helper classes which provide
    // a simple test functionality. The solution is quite easy...
    // we try to get the return value of the function we search for and
    // create a pointer from it and set it to default value nullptr.
    // if this works the overloaded method `test` returns the data type
    // one. If the first test function will not fit, we cat with ... all
    // other parameters which results in getting data type two.
    // After that we can setup an enum which evaluates `value` to
    // boolean true or false regarding to the comparison function.

    template <typename T>
    class has_Foo
    {
        using one = char;
        using two = struct { char a; char b;};
        template <typename C> static one test( typename std::remove_reference<decltype(std::declval<C>().Foo())>::type*  );  
        template <typename C> static two test( ... ) ; 
        public:
        enum { value = sizeof(test<T>(0)) == sizeof(char) };
        enum { Yes = sizeof(test<T>(0)) == sizeof(one) };
        enum { No = !Yes };
    };
    template <typename T>
    class has_Bar
    {
        using one = char;
        using two = struct { char a; char b;};
        template <typename C> static one test( typename std::remove_reference<decltype(std::declval<C>().Bar())>::type*  );  
        template <typename C> static two test( ... ) ; 
        public:
        enum { value = sizeof(test<T>(0)) == sizeof(char) };
        enum { Yes = sizeof(test<T>(0)) == sizeof(one) };
        enum { No = !Yes };
    };

    // Now in your adapter class you can use the test functions
    // to find out which function exists. If your class
    // contains a Foo function the first one compiles and if the
    // the class contains a Bar function the second one fits. SFINAE
    // disable the rest.
    // We need a call helper here because SFINAE only 
    // fails "soft" if the template parameter can deduced from the
    // given parameters to the call itself. So the method
    // Call forwards the type to test "T" to the helper method as 
    // as explicit parameter. Thats it!
    template <typename T>
    class X: public T
    {
        public:
            template < typename N, std::enable_if_t< has_Foo<N>::value>* = nullptr>
                void Call_Helper() { this->Foo(); }
            template < typename N, std::enable_if_t< has_Bar<N>::value>* = nullptr>
                void Call_Helper() { this->Bar(); }
            void Call() { Call_Helper<T>(); }
    };

    int main()
    {
        X<A> xa;
        X<B> xb;
        xa.Call();
        xb.Call();
    }