为什么模板参数推导不适用于仅指定前两个参数的可变参数模板类?

问题描述 投票:3回答:2

我有一个可变参数模板类,该类具有两个固定的模板参数以及一个可变的参数列表。创建实例时,我想指定前两个参数,并从传递给ctor的参数中推导出其余参数。但这不起作用,可变参数部分似乎总是空的。只有指定所有类型(包括ctor参数)时,我才能创建实例。

这是我用于测试的代码:

#include <iostream>
#include <tuple>
#include <string>

class Service
{
public:
  virtual void Serve() = 0;
};


class InterfaceA : public Service {};
class InterfaceB : public Service {};
class InterfaceC : public Service {};


class ImplementationA : public InterfaceA
{
  virtual void Serve() override
  {
    std::cout << "Implementation A: <null>";
  }
};


class ImplementationB : public InterfaceB
{
public:
  ImplementationB(int x)
    : m_x(x)
  {}

  virtual void Serve() override
  {
    std::cout << "Implementation B: " << std::to_string(m_x);
  }

private:
  int m_x = 0;
};


class ImplementationC : public InterfaceC
{
public:
  ImplementationC(std::string str)
    : m_str(str)
  {}

  virtual void Serve() override
  {
    std::cout << "Implementation C: " << m_str;
  }

private:
  std::string m_str;
};


template <typename Interface, typename Implementation, typename... CtorArgs>
class Wrapper
{
public:
  Wrapper(CtorArgs&&... args)
    : m_ctorArgs(std::make_tuple(std::forward<CtorArgs>(args)...))
  {}

  Service& GetService()
  {
    m_service = std::apply([](CtorArgs ... ctorArgs)
    {
      return std::make_unique<Implementation>(ctorArgs...);
    },
      m_ctorArgs);

    return *m_service;
  }

private:
  std::tuple<CtorArgs ...> m_ctorArgs;
  std::unique_ptr<Service> m_service;
};

// deduction guide, not working...
template <typename Interface, typename  Implementation, typename... CtorArgs>
Wrapper(int x)->Wrapper<Interface, Implementation, int>;


int main()
{
    Wrapper<InterfaceA, ImplementationA> wrapperA;
    wrapperA.GetService().Serve();
    std::cout << "\n";

//    Wrapper<InterfaceB, ImplementationB> wrapperB(7);       // NOT OK
    Wrapper<InterfaceB, ImplementationB, int> wrapperB(7);    // OK
    wrapperB.GetService().Serve();
    std::cout << "\n";
}

我想指定服务,但是在需要时根据需要创建它们(由于服务之间的依赖性)。我已经在生产代码中使用了工厂方法(包装器知道将哪些参数传递给服务ctor),但是在测试代码中,我希望能够快速为模拟和虚拟服务创建包装器,这些包装可能需要与生产中不同的参数服务。

我也试图指定一个演绎指南,但似乎没有效果...

c++ class templates variadic
2个回答
0
投票

您可能使用模板构造函数,并且将std::function用作工厂:

template <typename Interface, typename Implementation>
class Wrapper
{
public:
  template <typename... CtorArgs>
  Wrapper(CtorArgs&&... args)
    : m_factory([=](){return std::make_unique<Implementation>(ctorArgs...);})
  {}

  Service& GetService()
  {
    m_service = m_factory();
    return *m_service;
  }

private:
  std::function<std::unique_ptr<Service>()> m_factory;
  std::unique_ptr<Service> m_service;
};

推论指南是无用的,因为应该使用它来推导所有参数。

提供模板参数是全部还是什么都没有。

但是您可以这样做:

Wrapper<InterfaceB, ImplementationB> wrapperB(7); // Ok

0
投票

扣除指南“应该”是

template<typename Interface, typename Implementation, typename... CtorArgs>
Wrapper(CtorArgs&&... x)->Wrapper<Interface, Implementation, CtorArgs...>;

但是这不起作用,因为InterfaceImplementation是不可推论的。

我建议遵循标准库并改为使用工厂函数:

template<typename Interface, typename Implementation, typename... Args>
Wrapper<Interface, Implementation, Args...> make_wrapper(Args&&... args) {
    return Wrapper<Interface, Implementation, Args...>(std::forward<Args>(args)...);
}

int main() {
    auto wrapperA = make_wrapper<InterfaceA, ImplementationA>();
    wrapperA.GetService().Serve();
    std::cout << "\n";
}

另一个解决方案是将虚拟参数添加到Wrapper::Wrapper

template<typename T>
struct type_t { };
template<typename T>
constexpr inline type_t<T> type{};

template<typename Interface, typename Implementation, typename... CtorArgs>
class Wrapper {
public:
  Wrapper(type_t<Interface>, type_t<Implementation>, CtorArgs&&... args)
    : m_ctorArgs(std::make_tuple(std::forward<CtorArgs>(args)...))
  {}
// ...
};

// not needed anymore, is implicit
// template<typename Interface, typename Implementation, typename... CtorArgs>
// Wrapper(type_t<Interface>, type_t<Implementation>, CtorArgs&&... x)->Wrapper<Interface, Implementation, CtorArgs...>;

int main() {
    Wrapper wrapperB(type<InterfaceB>, type<ImplementationB>, 7);
    wrapperB.GetService().Serve();
    std::cout << "\n";
}

还有这个受OCaml启发的事物

template<typename Interface, typename Implementation>
struct Wrapper {
    template<typename... Args>
    class type {
    public:
        type(Args&&... args)
          : m_ctorArgs(std::make_tuple(std::forward<Args>(args)...))
        {}
    // ...
    };
};

int main() {
    std::string s("Hello!");
    // There's a spot of weirdness here: passing s doesn't work because then you end up trying to store a reference to s in the tuple
    // perhaps the member tuple should actually be std::tuple<std::remove_cvref<Args>...>
    Wrapper<InterfaceC, ImplementationC>::type wrapperC(std::move(s));
    wrapperC.GetService().Serve();
    std::cout << "\n";
}

旁注:Service::~Service()可能应该是virtual

© www.soinside.com 2019 - 2024. All rights reserved.