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

这是类(class)

#include <iostream>

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

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

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

因此,我想创建一个接口(interface),以将该类用于特定的请求和响应。

这里的界面
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;
};

这个想法是,有一个专门化需要两种参数类型,并为这两种类型定义sendRequest方法。
我将模板参数推送到该类,因此该方法可以是虚拟的。

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

函数可以根据其需要执行的操作,例如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);
}

但是我不想手动创建一个仅将 call 转发给客户端的类,我的时间比那更有值(value)。
让编译器为我努力!

这里的代码创建一个适配器,该适配器将客户端并遵守该接口(interface)。
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;
};

这次的想法是为给定用户使用的请求和响应类型的每个单个接口(interface)定义一个适配器,从它们派生出来,从而能够使用该类代替该接口(interface)。

梦想在这里破了。
 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的派生类型(而是仅派生其之前的层次结构中的所有类)。

至此,我对C++的了解停止了(我是从最近才开始的),而且我不知道如何解决该问题。

对我来说,第一个错误是没有道理的,因为确实确实这些函数在IClient接口(interface)中是纯函数,但我通过using Derived::method将所有这些函数“取消隐藏”到类中,因此可以在调用ClientAdapter时将其解析。

我也想知道为什么该方法在错误中使用相同的参数出现2次。

如何获得此代码进行编译并做正确的事情?

这是一个wandbox示例,用于查看错误并尝试对其进行弄弄。

谢谢

最佳答案

简化的复制:

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抽象。递归减少的ClientAdapter覆盖递归减少的sendRequest中的IClient方法这一事实并不重要,因为这是IClient的两个不同实例。

如果实际上在所有情况下都继承IClient,那么由于inheritance via dominance(Live demo),问题就消失了。请注意,这种继承方式会在MSVC中引发警告C4250。您可以放心地忽略它。

07-27 13:20