0

I'm just trying to write a very simple function with a variable number of arguments so I can write a function similar to printf for an assignment. After looking at the documentation for va_list I'm not sure why this code keeps giving me run-time errors:

enter image description here

Here is my code:

void print(string sOne , ...);
void main()
{
    print("first string", "second string", "third String");
    system("pause");
}

void print(string sOne , ...)
{
    va_list arguments;
    va_start(arguments, sOne);
     while ((va_arg(arguments, int)) != 0)
    {
        string printString = va_arg(arguments, string);
        cout << printString;
    }
    va_end(arguments);
}
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
Joe Blake
  • 27
  • 1
  • 6

3 Answers3

7

Your implementation of variadic function is very incorrect.

First, you need a some way to tell the function how many arguments there are or when they end. Standard printf does this by using format specifiers (their number represents the number of args), another option is to provide the number explicitly. You seem to expect the last argument to be integer 0 (strange choice btw.), but you never pass 0 as a last argument to your variadic function.

Second, you can't portably extract std::string from variadic function arguments. Only trivial types are fully supported, and for strings you have to use char*. std::string is not trivial, because it has non-trivial constructor and destructor. Some compilers do support non-trivial types as arguments for such functions, but others do not, so you shouldn't try this.

The last, but not the least: variadic functions have no place in C++ world, even for assignments.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • I disagree with your final statement. Varadic functions are essential to meta programming. – Jonathan Mee Apr 07 '16 at 14:48
  • @Jonathan Mee C++ has variadic templates and initializer_list's, what more do you need? va_list is just legacy cruft IMHO. – Jesper Juhl Apr 07 '16 at 15:22
  • 1
    @JesperJuhl, they were. Before expression SFINAE descendened on us. – SergeyA Apr 07 '16 at 15:26
  • You often need `va_list` to take advantage of Varadic Templates, though I do agree that in a lot of cases the encapsulations of such are coming of age. – Jonathan Mee Apr 07 '16 at 15:26
  • @JesperJuhl, I suppose, he referes to decades old trick for checking the presence of a member. – SergeyA Apr 07 '16 at 15:27
  • @JonathanMee, oh, this is not the thing I supposed? So how exactly variadic functions help us to take advantage of variadic templates? Care to provide an example? May be even as a separate answer? – SergeyA Apr 07 '16 at 15:27
  • @SergeyA So I miss-remembered [this question](http://stackoverflow.com/q/35893826/2642059) as using `va_list`. I stand corrected. Perhaps there is no more use of `va_list` O.O – Jonathan Mee Apr 07 '16 at 17:50
  • Your statement: "You can't extract `std::string` from variadic function arguments. Only primitive types are supported, and for strings you have to use `char*`." Is just wrong. The only reason that it didn't work is in part **3** of [my answer](http://stackoverflow.com/a/36483629/2642059). – Jonathan Mee Apr 07 '16 at 17:51
  • @SergeyA I shouldn't have said what? I don't mind downvotes if I can learn something from them, so please enlighten me. – Jonathan Mee Apr 07 '16 at 18:02
2

SergeyA explained why your code does not work, here is one of the possible solutions:

void print(const char *sOne , ...);
int main()
{
    print("first string", "second string", "third String", nullptr);
    system("pause");
}

void print(const char *sOne , ...)
{
    va_list arguments;
    va_start(arguments, sOne);
    while (sOne)
    {
        cout << sOne;
        sOne = va_arg(arguments, const char *);
    }
    va_end(arguments);
}

Again this example is in case you have to use C-style variadic function, you should consider using C++ variadic template instead.

Slava
  • 43,454
  • 1
  • 47
  • 90
0

So you have several problems with your implementation.

  1. You don't pass in a limit to the number of calls to va_arg and:

If va_arg is called when there are no more arguments in ap, or if the type of the next argument in ap (after promotions) is not compatible with T, the behavior is undefined

  1. You appear to want to print "first string" so you need to pass that as part of the va_list, you can clean up both 1 and 2 by using a count as your first argument: void print(int sOne, ...)
  2. You are passing in const char*s as arguments to your va_list and expecting it to promote those to strings. va_list will not promote for you. It casts whatever you passed in to the type specified in va_arg And if you try to treat a const char* as a string you will end up with a run-time error, as you have seen. This can be corrected by using strings as your va_list arguments: "first string"s "second string"s, "third String"s this is only conditionally supported in C++ You'll need to use va_arg(arguments, const char*)
  3. You are calling va_arg to advance your for loop. You'll need to use a counter for that. Calls to va_arg extract the next argument, they do not test if an argument is available. Instead use your count that you passed in as part of 2 for (auto i = 0; i < sOne; ++i)

Making those changes should get your code looking something like this:

void print(int sOne, ...) {
    va_list arguments;
    va_start(arguments, sOne);

    for (auto i = 0; i < sOne; ++i) {
        cout << va_arg(arguments, const char*) << endl;
    }
    va_end(arguments);
}

Live Example

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • Undefined behaviour. `If va_arg is called when ... the type of the next argument in ap (after promotions) is not compatible with T, the behavior is undefined...` – SergeyA Apr 07 '16 at 17:55
  • @SergeyA I agree with that... But I assume you posted it cause my answer doesn't seem to? Could you explain to me how your statement conflicts with what I said? – Jonathan Mee Apr 07 '16 at 18:00
  • The actual type of the argument in this case is `const char*`. `std::string` is not a compatible type for `const char*` and `const char*` can not be *promoted* to `std::string`. Thus `va_arg(arguments, string)` sports undefined behavior. – SergeyA Apr 07 '16 at 18:02
  • @SergeyA Did you read my answer before downvoting? Pay close attention to **3**: [`string::operator ""s`](http://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s) – Jonathan Mee Apr 07 '16 at 18:04
  • I got to admit, I missed ""s. It's still undefined, as per 5.2.2 / 7 (`std::string` has non-trivial copy constructor). Downvote stands. – SergeyA Apr 07 '16 at 18:12
  • @JonathanMee do you claim that non POD types can be passed through `va_arg`? Is there ground for it? – Slava Apr 07 '16 at 18:15
  • @SergeyA Fine, if I've done something wrong, but why should a non-trivial copy constructor matter? Ah, I get it you misquoted what you're quoting from: http://en.cppreference.com/w/cpp/utility/variadic/va_arg In your ellipsis there is the phrase; "there are no more arguments in ap, or if the type of the next argument in `ap` (after promotions) is not compatible with `T`" That means that the behavior *is* defined if there are more arguments of type `T` in the `va_list`. – Jonathan Mee Apr 07 '16 at 18:20
  • The first quote I gave you was not relevant (like I said, i missed that you are actually using `std::string` arguments, i thought they are still quoted literals). With ""s, this is now std::string, so this quote is not applicable. But now you are essentially violating clause 5.2.2 / 7 of the C++ standard (non-trivial types as arguments to ellipsis are conditionally supported in implementation-defined manner). – SergeyA Apr 07 '16 at 18:23
  • 2
    Here are details http://stackoverflow.com/questions/10083844/passing-non-pod-type-to-variadic-function-is-undefined-behavior – Slava Apr 07 '16 at 18:28
  • @Slava So gcc supports it: https://gcc.gnu.org/ml/gcc-patches/1999-07n/msg00606.html they do a `*((TYPE *) (void *) ((char *) (AP)...` so you're working with the actual object passed. Visual Studio does the same. – Jonathan Mee Apr 07 '16 at 18:32
  • @JonathanMee maybe so, but I would not recommend to use that, especially that there is a proper and safer solution is available in standard c++ – Slava Apr 07 '16 at 18:35
  • @SergeyA So... by violating it you mean I'm conditionally violating it? Note that your answer then is also conditionally wrong, as you say: "Second, you can't extract std::string from variadic function arguments. Only primitive types are supported, and for strings you have to use char*" However I will qualify my answer. – Jonathan Mee Apr 07 '16 at 18:35
  • @JonathanMee, congrats - you captured a very essence of undefined behavior. How about CC (Sun C++ compiler?) xlC? (AIX C++ compiler)? – SergeyA Apr 07 '16 at 18:36
  • @JonathanMee, you would be right if the question was tagged `gcc` (with given version). Since question is not tagged with this, it's general C++ question, and the answer to this is "Non-trivial types are not to be used". – SergeyA Apr 07 '16 at 18:37
  • @Slava I concede your point. It hardly makes sense to take a gamble on `string` when we can just use `const char*`. – Jonathan Mee Apr 07 '16 at 18:41
  • @SergeyA I will grudgingly admit that I have learned something. None-the-less, I still think your answer is "conditionally wrong". I offer an up-vote for your time if you are willing to qualify it. – Jonathan Mee Apr 07 '16 at 18:54
  • @SergeyA Your answer says: "Second, you can't extract `std::string` from variadic function arguments." This goes against the standard explicitly says that this is: "Conditionally-supported with implementation-defined semantics." – Jonathan Mee Apr 07 '16 at 18:58
  • Yeah, I got you mean *clarify* :). Edited the answer for the sake of clarity. Doubt the edit will be of any use to OP, but could be useful for someone else. And you do not have to upvote 'for my time'. If you do not think the answer has value on it's own, do not upvote. – SergeyA Apr 07 '16 at 19:00
  • @SergeyA I'm not up-voting you for the answer. I'm up-voting you as a thank you for taking the time to explain what was wrong with my answer. – Jonathan Mee Apr 07 '16 at 19:02