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

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

现在我有了模板class

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

我知道模板T只会被实例化成2个classes.

class D
{
public:
     void setB();
};

class R
{
public:
     void SetB();
};

我们可以看到,classD的函数名setB和R的函数SetB是不一样的。所以在模板 class b 中我不能只使用 setB。那么有没有办法修改D或者R呢?我可以在模板中添加一些包装器或技巧 class 来解决这个问题吗?

您可以针对具有不同语义的 class 专门化您的模板:

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

也许特质class可以帮助你:

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 指出的那样,此技术也称为 Tag Dispatching

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

如果您想切换被调用的方法,每个 class 中必须只存在其中一个方法。如果检查发现不止一种测试方法,我提供的方法将不起作用!

下面的例子是为C++14写的,如果你把新的库函数换成自己实现的(当然不方便),也可以用在C++03上

测试 class 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();
    }