简体   繁体   English

是否可以处理可变函数中的非原始类型?

[英]Is it possible to handle non-primitive types in a variadic function?

I have 2 versions of the same variadic function, however one works the other doesn't. 我有2个版本的同一个可变参数函数,但是一个版本有效,而另一个版本则无效。 I suspect the reason is because one is using a primitive type where the other uses std::string. 我怀疑原因是因为一个正在使用原始类型,而另一个正在使用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");
}

The above results in the following output: 上面的结果是以下输出:

Hallo
you
people
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

The first function thus works but the second doesn't. 因此,第一个功能有效,而第二个无效。 Am I correct to assume that it's because the variadic macro doesn't handle non-primitive types? 我是否可以正确地假设这是因为可变参数宏不能处理非原始类型? Can you make it handle non-porimitive types? 您可以让它处理非流行类型吗?

The answer to the first part is that you're mostly correct. 第一部分的答案是您基本上是正确的。 There are restrictions on the use of class types, in particular if they have a non-trivial copy constructor (which std::string has). 类类型的使用受到限制,特别是如果它们具有非平凡的副本构造函数( std::string具有)的话。 It might work on some implementations and not on others. 它可能适用于某些实现,而不适用于其他实现。 Formally, that's conditionally-supported with implementation-defined semantics . 形式上,这有条件地由实现定义的语义支持

The second part is a bit harder, but also a bit simpler. 第二部分比较难,但是也更简单。 These varargs are old C, and not typesafe. 这些varargs是旧的C,并且不是类型安全的。 C++ does have typesafe variadics, via templates: C ++通过模板确实具有类型安全的变量:

template<typename... T>
void test1(T... args)
{
    std::cout << ... << args << std::endl;
}

The problem is that you need to know how to unpack those args... - they're called parameter packs . 问题是您需要知道如何解压缩这些args...它们被称为参数包 There are a few different ways to unpack them; 打开它们的包装有几种不同的方法。 this particular form is called a fold expression. 这种特殊形式称为折叠表达式。

va_arg decodes the va_list va_arg解码va_list

You cannot use va_arg to convert the passed in variable argument parameter in a single go. 您不能一次使用va_arg转换传入的可变参数参数。 The syntax of va_arg would be to decode the variable argument to match the type that was passed in. For example, if you pass in an int value, you must decode it as an int with va_arg . va_arg的语法将是解码变量参数以匹配传入的类型。例如,如果传入int值,则必须使用va_arg将其解码为int You cause undefined behavior if you try to decode it as anything else (like, as a double ). 如果尝试将其解码为其他任何内容(例如, double ),则会导致未定义的行为。

Since you passed in a string literal, the type should be const char * . 由于您传入的是字符串文字,因此类型应为const char *

const char *arg = va_arg(file_names, const char *);

va_arg on non-trivial classes is implementation-defined 非平凡类的va_arg是实现定义的

As MSalter's answer clearly explains, even if you really passed in a std::string to a function expecting variable arguments, it is not necessarily going to work because the semantics are implementation-defined. 正如MSalter的答案清楚地解释的那样,即使您确实将std::string传递给了一个期望变量参数的函数,它也不一定会起作用,因为语义是实现定义的。

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.10). 如果给定参数没有参数,则以使接收函数可以通过调用va_arg(18.10)来获取参数值的方式传递参数。 … Passing a potentially-evaluated argument of class type (Clause 9) having a non- trivial copy constructor, a non-trivial move contructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics. …传递具有非平凡的复制构造函数,非平凡的移动构造函数或非平凡的析构函数(没有相应参数)的类类型的可能求值的参数,并且由实现定义的语义有条件地支持。 ...
C++.11 §[expr.call] ¶7 C ++。11§[expr.call]¶7

Note: (18.10) defines va_arg , and (Clause 9) is §[class]. 注意:(18.10)定义了va_arg ,而(条款9)是§[class]。

Just use a variadic template 只需使用可变参数模板

You can use C++11's variadic template argument feature to achieve the effect you want in a type safe way. 您可以使用C ++ 11的可变参数模板参数功能以类型安全的方式实现所需的效果。 Assume you actually want to do a little more than print each argument, the usual pattern is to recursively traverse the parameter pack, unpacking one parameter at a time. 假设您实际上想要做的不仅仅是打印每个参数,通常的模式是递归遍历参数包,一次解包一个参数。

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...);
}

Thus, iteration is achieved by recursively calling the function, reducing the parameter pack by one parameter on the next recursive call. 因此,可以通过递归调用函数来实现迭代,并在下一个递归调用中将参数包减少一个参数。

If you call test2 as such, it should work: 如果您这样调用test2,它将正常工作:

test2(3, std::string("Hallo"), std::string("you"), std::string("people"));

What's happening here is that when you pass "Hallo" you're passing a char*, not an std::string, so when you try to retrieve the char* by giving it a type of std::string with va_arg, it fails since the object isn't an std::string. 这里发生的是,当您传递“ Hallo”时,您传递的是char *,而不是std :: string,因此,当您尝试通过给va_arg提供std :: string类型的类型来检索char *时,它将失败因为该对象不是std :: string。

Also, in some compilers, it seems that you can't pass types that aren't trivially destructable, when I tried this in cpp.sh it gave the error 另外,在某些编译器中,您似乎无法传递不可分解的类型,当我在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 '...' 

Although it seems this isn't part of the standard as I couldn't find anywhere that required this, so I assume it's just a limitation of the compiler they're using. 尽管这似乎不是标准的一部分,因为我找不到任何需要它的地方,所以我认为这只是他们使用的编译器的限制。 If anyone knows the details about this, I'll edit the answer with the correct information. 如果有人知道这方面的详细信息,我将使用正确的信息来编辑答案。

Like Henri Menke said, it seems the problem is that cpp.sh is using an old version that isn't c++11 compliant, so if you use a compiler that is at least c++11 compliant, you can use std::string within variadic arguments. 就像Henri Menke所说的那样,似乎问题在于cpp.sh使用的是不兼容c ++ 11的旧版本,因此,如果使用的编译器至少兼容c ++ 11,则可以使用std: :可变参数中的字符串。

It appears this is an implementation detail, as miradulo said, so it will depend on your compiler, and not the standard version you're using. 正如miradulo所说,这似乎是一个实现细节,因此它将取决于您的编译器,而不是您使用的标准版本。

And also, like Henri Menke mentioned, if you use 而且,就像Henri Menke提到的那样,如果您使用

using std::string_literals;

And then put an 's' at the end of the normal string as such: 然后在普通字符串的末尾添加一个“ s”,如下所示:

test2(3, "Hallo"s, "you"s, "people"s);

It transforms the char* to a std::string so you can use them like this to call test2, this is due to string literals . 它将char *转换为std :: string,因此您可以像这样使用它们来调用test2,这是由于字符串常量

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM