我有一些代码要从使用重载函数转换为使用可变参数模板参数。
现有代码具有用于不同参数计数的多个模板化功能:
#include <iostream>
#include <string>
struct Boo
{
Boo(const std::string& name)
{
std::cout << "Boo constructor: " << name << std::endl;
};
static std::string CreateName(const std::string& name)
{
return "BooID:" + name;
}
};
struct Foo
{
Foo(const std::string& name)
{
std::cout << "Foo constructor: " << name << std::endl;
};
};
template <typename T>
T Construct(const std::string& name)
{
return T(T::CreateName(name));
}
template <typename T>
T Construct(const std::string& name1, const std::string& name2)
{
return T(T::CreateName(name1, name2));
}
// Class Foo doesn't have CreateName available
template<>
Foo Construct<Foo>(const std::string& name)
{
return Foo{"ID:" + name};
}
int main()
{
Construct<Boo>(std::string("123"));
Construct<Foo>(std::string("456"));
}
这将输出:
Boo constructor: BooID:123
Foo constructor: ID:456
如果我尝试用单个可变参数模板版本替换两个模板化的Construct()
函数,那么我将无法确定应如何仅为Foo
类指定特殊版本(在GCC上尝试4.9.2、8.1和Visual Studio 2015)。
template <class T, typename ...Args>
T Construct(Args... args)
{
return T(T::CreateName(args...));
}
// Class Foo needs some extra information when constructed with strings...
template<>
Foo Construct<Foo>(const std::string& name)
{
return Foo{"ID:" + name};
}
编译器尝试使用可变参数模板版本失败(因此CreateName
中没有Foo
)。一样:
template <class T, typename ...Args>
T Construct(Args... args)
{
return T(T::CreateName(args...));
}
// Class Foo needs some extra information when constructed with strings...
template<>
Foo Construct<Foo, const std::string&>(const std::string& name)
{
return Foo{"ID:" + name};
}
我可以通过制作通用模板并使用std::enable_if_t
来限制类型来使其工作:
template <class T, typename ...Args>
T Construct(Args... args)
{
return T(T::CreateName(args...));
}
// Class Foo needs some extra information when constructed with strings...
template<typename T, typename = std::enable_if_t<std::is_base_of<Foo, T>::value>>
T Construct(const std::string& name)
{
return T{"ID:" + name};
}
哪个不像原始的template<> Foo Construct<Foo>(...)
版本那样容易阅读。将std::enable_if_t
与可变参数模板一起使用是唯一的选择,还是有某种方法可以使期望的行为超载?
答案 0 :(得分:2)
这里的问题是专业化必须与基本模板匹配。
您的基本模板将被推导为Construct<Foo, std::string>
,并且您的专业化必须匹配此模板才能被视为有效。
如果您将专业化更改为
template<>
Foo Construct<Foo, std::string>(std::string name)
{
return Foo{"ID:" + name};
}
在gcc上可以正常使用。
答案 1 :(得分:1)
不要专门研究功能模板。最简单的方法是只使用类模板:
template <typename T, typename... Args>
T Construct(Args&&... args) {
return ConstructorImpl<T>::apply(std::forward<Args>(args)...);
}
这使得直接了解如何专长:
template <typename T>
struct ConstructorImpl {
template <typename... Args>
static T apply(Args&&... args) {
return T(T::CreateName(std::forward<Args>(args)...));
}
};
template <>
struct ConstructImpl<Foo> {
static Foo apply(std::string const& name) {
return Foo("ID:" + name});
}
};
使用此方法自然会向Construct
应用ConstructorImpl<T>::apply
可调用Args...
的约束。就像这样简单:
template <typename T, typename... Args>
auto Construct(Args&&... args)
-> decltype(ConstructorImpl<T>::apply(std::forward<Args>(args)...))
{
return ConstructorImpl<T>::apply(std::forward<Args>(args)...);
}
(尽管授予的权限不会检查它是否实际上返回了T
)
答案 2 :(得分:0)
问题是您不能部分专门化模板功能。
我建议从模板struct
传递模板(或者在Boo
专业化中不使用)方法func()
模板
struct Constr
{
template <typename ... Args>
static T func (Args const & ... names)
{ return T(T::CreateName(names...)); }
};
template <>
struct Constr<Foo>
{
static Foo func (std::string const & name)
{ return Foo{"ID:" + name}; }
};
通话变为
Constr<Boo>::func(std::string("123"));
Constr<Foo>::func(std::string("456"));