可变参数模板方法专业化
Variadic template method specialization
这是我目前拥有的代码:
class Foo
{
public:
template<typename T, typename... Args>
void Function(T t1, Args... args){
// Definition
}
private:
template<typename T>
void Function(T t1){
// Definition
}
};
#include "header.h"
int main()
{
Foo foo;
foo.Function(1, 2, 3, 4, 5);
return 0;
}
工作正常。当我尝试将定义分隔为 source.cpp 时,gcc 开始抱怨。我知道我必须专门化模板以分离定义,所以我尝试将下面的代码添加到头文件中:
template<>
void Foo::Function<int, int...>(int t1, int... args);
template<>
void Foo::Function<int>(int);
但没有成功。我错过了什么
编辑:gcc 错误消息:
header.h:15:28: error: expansion pattern ‘int’ contains no argument
packs void Foo::Function(int t1, int... args);
header.h:15:48: error: expansion pattern ‘int’ contains no argument
packs void Foo::Function(int t1, int... args);
您不能使用 int...
作为参数包,所以这不起作用。此外,要将源代码与定义分开,您必须完全指定模板,因此即使允许该语法,int...
也不起作用。
解决这个问题的方法。
1.使 Function
接受初始化列表。
我们可以编写函数,使其接受 int
s:
的初始化列表
#include <initializer_list>
class Foo {
public:
void Function(int t1, std::initializer_list<int> t2);
};
void Foo::Function(int t1, std::initializer_list<int> t2) {
for(int i : t2) {
// stuff
}
}
现在,您可以非常轻松地调用 Function
,甚至没有模板化:
Foo f;
f.Function(10, {1, 2, 3, 4, 5});
如果您在其他地方使用模板,您可以将参数包直接扩展到初始化列表中:
template<class... Args>
void invoke_foo(Foo& f, int first, Args... rest) {
f.Function(first, {rest...});
}
2。使用 SFINAE 禁用所有 non-int 重载。 我们可以禁用 Foo::Function
的所有重载,这些重载不仅接受 int
s
#include <type_traits>
class Foo {
public:
// Requires C++17 for std::conjunction
// You could write your own std::conjunction too if you'd prefer
template<class... Args>
auto Function(int t1, Args... t2)
-> std::enable_if_t<std::conjunction<std::is_same<Args, int>...>::value>
{
// stuff
}
};
这样做的缺点是 non-integral 值不会自动转换为 int
。
有更好的方法。
首先,您似乎想要强制使用相同类型的所有参数(这是由 std::initializer_list
在接受的答案中完成的)。这可以通过提供额外的显式参数来强制执行:
class Foo
{
public:
template<typename T, typename... Args>
void Function(T t1, T t2, Args... args)
{
LOG;
this->Function(t1);
this->Function(t2, args...);
}
private:
template<typename T>
void Function(T t1)
{
LOG << VAR(t1);
}
};
template<>
void Foo::Function<int>(int x)
{
LOG << " Spec" << VAR(x);
}
如您所见,如果您为单个参数提供方法的专门化就足够了。
这是我目前拥有的代码:
class Foo
{
public:
template<typename T, typename... Args>
void Function(T t1, Args... args){
// Definition
}
private:
template<typename T>
void Function(T t1){
// Definition
}
};
#include "header.h"
int main()
{
Foo foo;
foo.Function(1, 2, 3, 4, 5);
return 0;
}
工作正常。当我尝试将定义分隔为 source.cpp 时,gcc 开始抱怨。我知道我必须专门化模板以分离定义,所以我尝试将下面的代码添加到头文件中:
template<>
void Foo::Function<int, int...>(int t1, int... args);
template<>
void Foo::Function<int>(int);
但没有成功。我错过了什么
编辑:gcc 错误消息:
header.h:15:28: error: expansion pattern ‘int’ contains no argument packs void Foo::Function(int t1, int... args);
header.h:15:48: error: expansion pattern ‘int’ contains no argument packs void Foo::Function(int t1, int... args);
您不能使用 int...
作为参数包,所以这不起作用。此外,要将源代码与定义分开,您必须完全指定模板,因此即使允许该语法,int...
也不起作用。
解决这个问题的方法。
1.使 Function
接受初始化列表。
我们可以编写函数,使其接受 int
s:
#include <initializer_list>
class Foo {
public:
void Function(int t1, std::initializer_list<int> t2);
};
void Foo::Function(int t1, std::initializer_list<int> t2) {
for(int i : t2) {
// stuff
}
}
现在,您可以非常轻松地调用 Function
,甚至没有模板化:
Foo f;
f.Function(10, {1, 2, 3, 4, 5});
如果您在其他地方使用模板,您可以将参数包直接扩展到初始化列表中:
template<class... Args>
void invoke_foo(Foo& f, int first, Args... rest) {
f.Function(first, {rest...});
}
2。使用 SFINAE 禁用所有 non-int 重载。 我们可以禁用 Foo::Function
的所有重载,这些重载不仅接受 int
s
#include <type_traits>
class Foo {
public:
// Requires C++17 for std::conjunction
// You could write your own std::conjunction too if you'd prefer
template<class... Args>
auto Function(int t1, Args... t2)
-> std::enable_if_t<std::conjunction<std::is_same<Args, int>...>::value>
{
// stuff
}
};
这样做的缺点是 non-integral 值不会自动转换为 int
。
有更好的方法。
首先,您似乎想要强制使用相同类型的所有参数(这是由 std::initializer_list
在接受的答案中完成的)。这可以通过提供额外的显式参数来强制执行:
class Foo
{
public:
template<typename T, typename... Args>
void Function(T t1, T t2, Args... args)
{
LOG;
this->Function(t1);
this->Function(t2, args...);
}
private:
template<typename T>
void Function(T t1)
{
LOG << VAR(t1);
}
};
template<>
void Foo::Function<int>(int x)
{
LOG << " Spec" << VAR(x);
}
如您所见,如果您为单个参数提供方法的专门化就足够了。