我想知道我是否可以使用由明确指定的单一类型组成的参数包。例如,像这样:
#include <iostream>
using namespace std;
void show() { }
template<typename First, typename... Rest>
void show(First f, Rest... rest)
{
cout << f << endl;
show(rest...);
}
void foo(int f, int... args) // error
{
show(f, args...);
}
int main()
{
foo(1, 2, 3);
}
我遇到的问题是foo()
的定义。使用OS X clang ++版本5(llvm 3.3svn),我收到错误error: type 'int' of function parameter pack does not contain any unexpanded parameter packs
。
当然,我可以通过将foo()
更改为函数模板来编译它:
template<typename... Args>
void foo(int f, Args... args)
{
show(f, args...);
}
但是现在foo()
将接受int
作为第一个参数,其余任何输出都可以流式传输。例如:
struct x { };
ostream& operator<<(ostream& o, x)
{
o << "x";
return o;
}
int main()
{
foo(1, 2, x(), 3); // compiles :(
}
现在,我看到the accepted solution here建议使用类型特征和std::enable_if
,但这很麻烦。他们还建议使用std::array
,但我认为一个简单的std::initializer_list
效果很好,看起来更干净,就像这样:
void foo_impl(initializer_list<int> ints)
{
for(int i: ints)
cout << i << endl;
}
template<typename... Args>
void foo(int f, Args... args)
{
foo_impl({f, args...});
}
struct x { };
ostream& operator<<(ostream& o, x)
{
o << "x";
return o;
}
int main()
{
foo(1, 2, 3);
foo(1, 2, x(), 3); // no longer compiles
// we also get an error saying no known conversion from 'x' to 'int' :)
}
所以这很整洁。但问题仍然存在,这是必要的吗?真的没有办法定义一个接受特定类型参数包的非模板函数吗?像这样:
void foo(int... args) { }
答案 0 :(得分:22)
void foo(int... args) {}
不,你不能写那个。
但是你可以用这种方法产生同样的效果:
template<typename ...Ints>
void foo(Ints... ints)
{
int args[] { ints... }; //unpack ints here
//use args
}
使用此方法,您可以根据需要传递所有int
。如果传递给foo
的任何参数不是int
或者可以转换为int
,则上述代码将导致编译错误,因为如果它int ...args
方法就是这种情况被允许。
如果您想要这种行为,您还可以使用static_assert
确保所有Ints
确实int
:
template<typename ...Ints>
void foo(Ints... ints)
{
static_assert(is_all_same<int, Ints...>::value, "Arguments must be int.");
int args[] { ints... }; //unpack ints here
//use args
}
现在你要实现is_all_same
元功能,这个功能并不难实现。
好的,这是基本的想法。您可以使用可变参数模板编写更复杂的代码,并借助一些实用程序元函数和辅助函数。
对于使用可变参数进行的大量工作,您甚至不需要存储在args[]
数组中,例如,如果要将参数打印到std::ostream
,那么您可以这样做:
struct sink { template<typename ...T> sink(T && ... ) {} };
template<typename ...Ints>
void foo(Ints... ints)
{
//some code
sink { (std::cout << ints)... };
}
在此创建一个sink
类型的临时对象,以便您使用list-initialization语法解压缩模板参数。
最后你可以使用std::initializer_list<int>
本身:
void foo(initializer_list<int> const & ints)
{
}
或std::vector<int>
,以防foo()
中需要类似矢量的行为。如果您使用其中任何一项,则在调用函数时必须使用{}
:
f({1,2,3});
这可能不太理想,但我认为随着C ++ 11的出现,你会经常看到这样的代码!
答案 1 :(得分:3)
为什么foo_impl
解决方法,而不是直接在initialize_list<int>
的签名中使用foo
?它阐明了您接受所述类型的可变大小参数列表。
答案 2 :(得分:1)
您可以指定要显示的类型:
#include <iostream>
template<typename T>
void show_type() {}
template<typename T, typename... Rest>
void show_type(const T& x, Rest... rest)
{
std::cout << x << std::endl;
show_type<T>(rest...);
}
template<typename... Args>
void foo(int x, Args... args)
{
show_type<int>(x, args...);
}
struct X { };
std::ostream& operator<<(std::ostream& o, X)
{
o << "x";
return o;
}
int main()
{
foo(1, 2, 3);
foo(1, 2, 3.0); // Implicit conversion
// or just
show_type<int>(1, 2, 3);
foo(1, 2, X()); // Fails to compile
}
答案 3 :(得分:0)
我意识到这是标记为C ++ 11,但C ++ 17 / 1z的功能在这里非常有用,所以我认为它的解决方案值得发布:
template<typename... Ints>
void foo(Ints... xs)
{
static_assert(std::conjunction<std::is_integral<Ints>...>::value);
(std::cout << xs << '\n', ...);
}
答案 4 :(得分:0)
与 Brian 的回答一样,我意识到这最初是为 C++11 设计的,但在 C++20 中,这可以使用概念以非常简单的方式解决:
#include <concepts>
void f(std::integral auto... ints)
{
// ...
}
std::integral
接受任何整数类型,所以它更通用一点,如果可以接受的话。如果没有,您可以执行以下操作:
#include <concepts>
template<class T>
concept exactly_int = std::same_as<int,T>;
void f(exactly_int auto... ints)
{
// ...
}
要对此多加一点解释,auto
本质上是一个隐式模板,它之前的名称限制了允许的类型。因此,在第一个示例中,将允许满足 std::integral
(int
、long
、unsigned
、char
等)的任何内容。第二个只允许 int
,因为这是唯一满足定义的概念的类型。