多模板接口继承名隐藏

Multiple templated interface inheritance name hiding

我有一个 class 带有发出请求的模板化方法。

这是class

#include <iostream>

struct Client
{
    template<class Request, class Response>
    void sendRequest(const Request& q , Response& a)
    {
        std::cout << q << " " << a << "\n";
    }
};

现在,一个方法发出未知类型的请求是不常见的。大多数方法都会发送一些请求类型,我希望这些方法使用接口以使代码可测试并清楚地表达它们对这些请求的依赖性。

但我们都知道我们不能将模板化方法虚拟化。

所以我想创建一个接口来使用 class 来处理特定的请求和响应。

这里是界面

template<class Q, class A, class ... Rest>
struct IClient : public IClient<Rest ...>
{
    using IClient<Rest ...>::sendRequest;
    virtual void sendRequest(const Q& , A&) = 0;

    ~IClient() = default;
};

template<class Q, class A>
struct IClient<Q, A>
{
    virtual void sendRequest(const Q& , A&) = 0;
    ~IClient() = default;
};

想法是有一种特殊化,它采用 2 种参数类型并为这 2 种类型定义 sendRequest 方法。 我将模板参数推送到 class 因此该方法可以是虚拟的。

泛型 class 派生自使用模板参数包的前 2 个参数创建的特化,因此我们可以定义多个请求和响应类型。

一个函数可以根据它需要做的请求像void foo(IClient<ReqA, RespA> client);void foo(IClient<ReqA, RespA, ReqB, RespB> client);那样使用它。

此时,从调用代码中可以清楚地看出该函数将发出哪些请求。如果我直接传递客户端,我会丢失该信息(除了无法模拟客户端)。

到目前为止一切顺利。

以后就用这个作为我们的测试代码吧

void foo(IClient<int, char, int, int, double, float>& client)
{
    int i = 10;
    char c = 'd';
    double d = 3.14;
    float f = 100.5f;
    client.sendRequest(i, c);
    client.sendRequest(i, i);
    client.sendRequest(d, f);
}

int main()
{
    Client client;
    ClientAdapter<int, char, int, int, double, float> adapter(client);
    foo(adapter);
}

但我不想手动创建一个 class 只是将呼叫转发给客户端,我的时间比这更宝贵。 让编译器为我努力吧!

这里是创建适配器的代码,它接受客户端并遵守接口。

template<class Q, class A, class ... Rest>
struct ClientAdapter: public IClient<Q, A, Rest ...>, public ClientAdapter<Rest ...>
{
    using ClientAdapter<Rest ...>::sendRequest;

    Client& client;
    ClientAdapter(Client& c) : ClientAdapter<Rest...>(c), client(c)
    {

    }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapter() = default;
};

template<class Q, class A>
struct ClientAdapter<Q, A> : public IClient<Q, A>
{
    Client& client;
    ClientAdapter(Client& c) : client(c) { }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapter() = default;
};

这次的想法是在给定用户使用的请求和响应类型的情况下为每个接口定义一个适配器,从它们派生,因此能够使用 class 代替接口。

这里梦想破灭了。

 In function 'int main()':
 70:55: error: cannot declare variable 'adapter' to be of abstract type 'ClientAdapter<int, char, int, int, double, float>' 
 30:8: note: because the following virtual functions are pure within 'ClientAdapter<int, char, int, int, double, float>': 
25:18: note: void IClient<Q, A>::sendRequest(const Q&, A&) [with Q = double; A = float]
25:18: note: void IClient<Q, A>::sendRequest(const Q&, A&) [with Q = double; A = float]
17:18: note: void IClient<Q, A, Rest>::sendRequest(const Q&, A&) [with Q = int; A = int; Rest = {double, float}]

如果我将适配器定义为

template<class Q, class A, class ... Rest>
struct ClientAdapter: public ClientAdapter<Q, A>, public ClientAdapter<Rest ...>

我得到一个

In function 'int main()':
71:16: error: invalid initialization of reference of type 'IClient<int, char, int, int, double, float>&' from expression of type 'ClientAdapter<int, char, int, int, double, float>'
56:6: note: in passing argument 1 of 'void foo(IClient<int, char, int, int, double, float>&)'

我认为这是因为 ClientAdapter 不是 IClientAdapter 的派生类型(但只派生出它之前的层次结构中的所有 classes)。

至此,我对 C++ 的了解停止了(我最近才开始学习),我不知道如何解决这个问题。

对我来说,第一个错误没有意义,因为虽然 IClient 接口中的这​​些函数确实是纯函数,但我 "name unhidden" 将它们全部放入 class通过 using Derived::method,因此可以在调用 ClientAdapter.

时解决它们

我也想知道为什么错误中出现两次相同参数的方法

我怎样才能让这段代码编译并做正确的事情?

这里有一个 wandbox 示例,可以查看错误并尝试 fiddle。

谢谢

简化复制:

struct A {
    virtual void foo() = 0;
};

struct B {
    virtual void foo() {}
};

struct C : A, B {
    using B::foo;
};

C c; // error: C is abstract

问题是 using B::foo 没有覆盖 A::foo

它与您的代码有什么关系?

template<class Q, class A, class ... Rest>
struct ClientAdapter: public IClient<Q, A, Rest ...>, public ClientAdapter<Rest ...>
{   
    using ClientAdapter<Rest ...>::sendRequest; ...        
    void sendRequest(const Q& q, A& a) override ...
};

您使用多个纯虚方法继承 IClient 但仅重写一个。其余的仍然是纯粹的,使 ClientAdapter 抽象。 recursively-reduced ClientAdapter 覆盖 recursively-reduced IClient 中的 sendRequest 方法这一事实并不重要,因为它们是 IClient 的两个不同实例。

如果您几乎在所有情况下都继承了 IClient,那么问题就会因为 MSVC 中的 inheritance via dominance (Live demo). Note that this mode of inheritance provokes a warning C4250 而消失。您可以放心地忽略它。

为了完整起见,我将添加另一个答案,该答案是在我的问题得到解决后发现的,并受到 Virtual inheritance: Why does it work when only one base class has "virtual" keyword? Is there a better way? 第一个答案的启发。

此解决方案避免使用虚拟继承。

想法是将结构线性化并使用模板将接口继承下推。

IClient 保持不变。

template<class Interface, class Q, class A, class ... Rest>
struct ClientAdapterImpl: public ClientAdapterImpl<Interface, Rest ...>
{
    using ClientAdapterImpl<Interface, Rest ...>::sendRequest;

    Client& client;
    ClientAdapterImpl(Client& c) : ClientAdapterImpl<Interface, Rest...>(c), client(c)
    {

    }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapterImpl() = default;
};

template<class Interface, class Q, class A>
struct ClientAdapterImpl<Q, A> : public Interface
{
    Client& client;
    ClientAdapterImpl(Client& c) : client(c) { }

    void sendRequest(const Q& q, A& a) override
    {
        client.sendRequest(q, a);
    }

    ~ClientAdapterImpl() = default;
};

template<class Q, class A, class ... Rest>
struct ClientAdapter: public ClientAdapterImpl<IClient<Q, A, Rest...>, Q, A, Rest ...>
{
     ClientAdapter(Client& c) : ClientAdapterImpl<IClient<Q, A, Rest...>, Q, A, Rest...>(c)
    {

    }

    ~ClientAdapter() = default;

}

此解决方案将接口作为模板参数传递到实现模板中,该模板从链的末端继承,有效地创建了一个平面层次结构。

interface0 <-- everything derive from this
    |
interface1
    |
interface2
    \
     \
   implementation0
          |
   implementation1
          ^
          |
   implementation3 <-- this derives from everything