[英]Is it possible to handle non-primitive types in a variadic function?
我有2个版本的同一个可变参数函数,但是一个版本有效,而另一个版本则无效。 我怀疑原因是因为一个正在使用原始类型,而另一个正在使用std :: string。
void test1(int f_num, ...)
{
va_list file_names;
va_start(file_names, f_num);
for(int i=0; i<f_num; i++)
{
string name = string(va_arg(file_names, char*));
cout << name << endl;
}
va_end(file_names);
}
void test2(int f_num, ...)
{
va_list file_names;
va_start(file_names, f_num);
for(int i=0; i<f_num; i++)
{
string name = va_arg(file_names, string);
cout << name << endl;
}
va_end(file_names);
}
int main()
{
test1(3, "Hallo", "you", "people");
test2(3, "Hallo", "you", "people");
}
上面的结果是以下输出:
Hallo
you
people
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Aborted (core dumped)
因此,第一个功能有效,而第二个无效。 我是否可以正确地假设这是因为可变参数宏不能处理非原始类型? 您可以让它处理非流行类型吗?
第一部分的答案是您基本上是正确的。 类类型的使用受到限制,特别是如果它们具有非平凡的副本构造函数( std::string
具有)的话。 它可能适用于某些实现,而不适用于其他实现。 形式上,这有条件地由实现定义的语义支持 。
第二部分比较难,但是也更简单。 这些varargs是旧的C,并且不是类型安全的。 C ++通过模板确实具有类型安全的变量:
template<typename... T>
void test1(T... args)
{
std::cout << ... << args << std::endl;
}
问题是您需要知道如何解压缩这些args...
它们被称为参数包 。 打开它们的包装有几种不同的方法。 这种特殊形式称为折叠表达式。
va_arg
解码va_list
您不能一次使用va_arg
转换传入的可变参数参数。 va_arg
的语法将是解码变量参数以匹配传入的类型。例如,如果传入int
值,则必须使用va_arg
将其解码为int
。 如果尝试将其解码为其他任何内容(例如, double
),则会导致未定义的行为。
由于您传入的是字符串文字,因此类型应为const char *
。
const char *arg = va_arg(file_names, const char *);
va_arg
是实现定义的 正如MSalter的答案清楚地解释的那样,即使您确实将std::string
传递给了一个期望变量参数的函数,它也不一定会起作用,因为语义是实现定义的。
如果给定参数没有参数,则以使接收函数可以通过调用va_arg(18.10)来获取参数值的方式传递参数。 …传递具有非平凡的复制构造函数,非平凡的移动构造函数或非平凡的析构函数(没有相应参数)的类类型的可能求值的参数,并且由实现定义的语义有条件地支持。 ...
C ++。11§[expr.call]¶7
注意:(18.10)定义了va_arg
,而(条款9)是§[class]。
您可以使用C ++ 11的可变参数模板参数功能以类型安全的方式实现所需的效果。 假设您实际上想要做的不仅仅是打印每个参数,通常的模式是递归遍历参数包,一次解包一个参数。
void test3 (int f_num, std::string file_name) {
std::cout << f_num << ':' << file_name << std::endl;
}
template <typename... T>
void test3 (int f_num, std::string file_name, T...rest) {
test3(f_num, file_name);
test3(f_num, rest...);
}
因此,可以通过递归调用函数来实现迭代,并在下一个递归调用中将参数包减少一个参数。
如果您这样调用test2,它将正常工作:
test2(3, std::string("Hallo"), std::string("you"), std::string("people"));
这里发生的是,当您传递“ Hallo”时,您传递的是char *,而不是std :: string,因此,当您尝试通过给va_arg提供std :: string类型的类型来检索char *时,它将失败因为该对象不是std :: string。
另外,在某些编译器中,您似乎无法传递不可分解的类型,当我在cpp.sh中尝试此操作时,它给出了错误
In function 'void test2(int, ...)': 27:23: error: cannot receive objects of non-trivially-copyable type 'std::string {aka class std::basic_string<char>}' through '...'; In function 'int main()': 36:51: error: cannot pass objects of non-trivially-copyable type 'std::string {aka class std::basic_string<char>}' through '...'
尽管这似乎不是标准的一部分,因为我找不到任何需要它的地方,所以我认为这只是他们使用的编译器的限制。 如果有人知道这方面的详细信息,我将使用正确的信息来编辑答案。
就像Henri Menke所说的那样,似乎问题在于cpp.sh使用的是不兼容c ++ 11的旧版本,因此,如果使用的编译器至少兼容c ++ 11,则可以使用std: :可变参数中的字符串。
正如miradulo所说,这似乎是一个实现细节,因此它将取决于您的编译器,而不是您使用的标准版本。
而且,就像Henri Menke提到的那样,如果您使用
using std::string_literals;
然后在普通字符串的末尾添加一个“ s”,如下所示:
test2(3, "Hallo"s, "you"s, "people"s);
它将char *转换为std :: string,因此您可以像这样使用它们来调用test2,这是由于字符串常量 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.