为什么模板参数推导不适用于仅指定前两个参数的可变参数模板 class?
Why is template parameter deduction not working with a variadic template class where only the first two parameters are specified?
我有一个可变参数模板 class,它采用两个固定模板参数和一个可变参数列表。
当我创建一个实例时,我想指定前两个参数,然后从传递给构造函数的参数中推导出其余参数。
但它不起作用,可变参数部分似乎总是空的。当我指定所有类型(包括构造函数参数)时,我只能创建一个实例。
这是我用来测试的代码:
#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 的参数),但是在测试代码中,我希望能够快速为模拟和虚拟服务创建包装器,这可能需要不同的参数作为生产服务。
我也试过指定推导指南,但是好像没有效果...
您可以使用模板构造函数,并且 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
演绎指南"should"是
template<typename Interface, typename Implementation, typename... CtorArgs>
Wrapper(CtorArgs&&... x)->Wrapper<Interface, Implementation, CtorArgs...>;
但这行不通,因为 Interface
和 Implementation
是不可推导的。
我建议遵循标准库并改用工厂函数:
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 启发 thing:
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
。
我有一个可变参数模板 class,它采用两个固定模板参数和一个可变参数列表。 当我创建一个实例时,我想指定前两个参数,然后从传递给构造函数的参数中推导出其余参数。 但它不起作用,可变参数部分似乎总是空的。当我指定所有类型(包括构造函数参数)时,我只能创建一个实例。
这是我用来测试的代码:
#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 的参数),但是在测试代码中,我希望能够快速为模拟和虚拟服务创建包装器,这可能需要不同的参数作为生产服务。
我也试过指定推导指南,但是好像没有效果...
您可以使用模板构造函数,并且 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
演绎指南"should"是
template<typename Interface, typename Implementation, typename... CtorArgs>
Wrapper(CtorArgs&&... x)->Wrapper<Interface, Implementation, CtorArgs...>;
但这行不通,因为 Interface
和 Implementation
是不可推导的。
我建议遵循标准库并改用工厂函数:
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 启发 thing:
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
。