3

I'm trying to create a function to assign default or input values to several (scalar) parameters using variadic/variable input arguments as:

void set_params(const vector<double> &input, int n, ...) {  
  va_list args;
  va_start (args, n);
  for (int i = 0; i < n; i++) {
    if (i < input.size()) {
      va_arg(args, int) = input[i];
    }
  }
  va_end(args);
}

int a = 1, b = 2, c = 3;
set_params({10, 20}, 3, a, b, c);

However, I'm getting the error on the assignment va_arg(args, int) = input[i]. Is it possible somehow to do assignment with variable arguments, or is there a better way to achieve this?

Patrick Kwok
  • 42
  • 10
  • Does this answer your question? [Is it possible to change the value of va\_arg?](https://stackoverflow.com/questions/36531090/is-it-possible-to-change-the-value-of-va-arg) – Yves Feb 21 '22 at 10:18
  • If you want to modify the parameter in `va_arg`, you have to pass it by pointer, instead of by reference or by value. Besides, don't use variadic arguments mechanism if possible. There are many other methods to do the same thing but more friendly. – Yves Feb 21 '22 at 10:19
  • 1
    [C++ core guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#F-varargs) advice _against_ the use of `va_args` etc. – JHBonarius Feb 21 '22 at 10:47

2 Answers2

6

Instead of using C's va_ stuff, C++ has it's own variadic template arguments, which you should preferably use

I'm no expert on this, but it could look a little bit like

#include <vector>
#include <iostream>

template <typename... Arg>
void set_params(const std::vector<double> &input, Arg&... arg) {
    unsigned int i{0};
    (
        [&] {
            if (i < size(input)) {
                arg = input[i++];
            }
        }(), // immediately invoked lambda/closure object
        ...); // C++17 fold expression
}

int main() {
    int a = 1, b = 2, c = 3;
    set_params({10, 20}, a, b, c);

    std::cout
        << a << ' '
        << b << ' '
        << c << '\n';
}
JHBonarius
  • 10,824
  • 3
  • 22
  • 41
  • 2
    You forgot to call your lambda, just add `()` after lambda's body. After that your code works. – Arty Feb 21 '22 at 11:30
  • 2
    I'd pass on to a function with a `std::index_sequence` rather than have `i++` https://coliru.stacked-crooked.com/a/1883e288d92a8a87 – Caleth Feb 21 '22 at 11:35
  • 1
    This is brilliant. Maybe a little bit more of background explanation would be suitable. This uses a C++17 [_fold expression_](https://en.cppreference.com/w/cpp/language/fold) with the comma operator. That way a lambda expression can be mapped over the variadic arguments. – Jakob Stark Feb 21 '22 at 11:45
  • Thanks for all the feedback – JHBonarius Feb 21 '22 at 12:39
1

If you want to change a variable, that is passed as function parameter, you must use a pointer or a reference. For example the following function will have no effect, since x is passed by value and not by reference.

void set_zero(int x) {
    x = 0;
}

In C++ you have two options to pass something by reference. Either pass a pointer or pass a lvalue reference:

void set_zero(int *x) {
    *x = 0;
}

void set_zero(int &x) {
    x = 0;
}

Both of these will change the original variable to zero. I haven't found a way of passing a lvalue reference though a variadic function call, so I suggest you use pointers. In your case the code would look like

void set_params(const vector<double> &input, int n, ...) {  
    va_list args;
    va_start (args, n);
    for (int i = 0; i < n; i++) {
        if (i < input.size()) {
            va_arg(args, int*) = input[i];
        }
    }
    va_end(args);
}

int a = 1, b = 2, c = 3;
set_params({10, 20}, 3, &a, &b, &c);

That beeing set, variadic arguments are a very unsafe way of passing variables. In your example, if someone changes the type of a, b, and c to another type, you will run into serious problems, as the type used in the set_params function no longer matches the type of the variables passed. You could do the whole function in a more safe way by e.g. using a vector also for the pointers.

template<typename T>
void set_params(const vector<T> &input, const vector<T*> &variables) {
    for ( int i = 0; i < variables.size(); i++ ) {
        if ( i < input.size() ) {
            *variables[i] = input[i];
        } else {
            break;
        }
    }
}

int a = 1, b = 2, c = 3;
set_params<int>({10,20}, {&a, &b, &c});
Jakob Stark
  • 3,346
  • 6
  • 22