0

I know how to use variadic templates and ellipses to accept a variable number of arguments, but how do you pass a variable number of arguments into a function?

Take the following code for example:

#include <iostream>

struct A {
   A(int a, int b) : x(a), y(b) {}
   int x, y;
};

struct B {
    B(int a, int b, int c) : x(a), y(b), z(c) {}
    int x, y, z;
};

template<typename T, typename... TArgs>
T* createElement(TArgs&&... MArgs) {
    T* element = new T(std::forward<TArgs>(MArgs)...);
    return element;
}

int main() {

    int Aargs[] = { 1, 2 };
    int Bargs[] = { 1, 2, 3 };

    A* a = createElement<A>(Aargs); //ERROR
    B* b = createElement<B>(Bargs); //ERROR

    std::cout << "a.x: " << a->x << "\na.y: " << a->y << "\n" << std::endl;
    std::cout << "b.x: " << b->x << "\nb.y: " << b->y << "\nb.z: " << b->z << "\n" << std::endl;

    delete a;
    delete b;
}

Is there any way to expand the arrays so that each of their values is like an argument being passed to the function (similar to parameter pack expansion)?

Or, if not, is there any other way to make this work?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770

1 Answers1

2

You can expand the array using a std::index_sequence

#include <iostream>
#include <utility>

struct A {
   A(int a, int b) : x(a), y(b) {}
   int x, y;
};

struct B {
    B(int a, int b, int c) : x(a), y(b), z(c) {}
    int x, y, z;
};

template<typename T, typename... TArgs>
T* createElement(TArgs&&... MArgs) {
    T* element = new T(std::forward<TArgs>(MArgs)...);
    return element;
}

template<typename T, typename U, size_t...  I>
T* createElementFromArrayHelper(std::index_sequence<I...>, U* a){
    return createElement<T>(a[I]...);
}

template<typename T, typename U, size_t N>
T* createElementFromArray(U (&a)[N]){
    return createElementFromArrayHelper<T>(std::make_index_sequence<N>{}, a);
}

int main() {

    int Aargs[] = { 1, 2 };
    int Bargs[] = { 1, 2, 3 };

    A* a = createElementFromArray<A>(Aargs); 
    B* b = createElementFromArray<B>(Bargs);

    std::cout << "a.x: " << a->x << "\na.y: " << a->y << "\n" << std::endl;
    std::cout << "b.x: " << b->x << "\nb.y: " << b->y << "\nb.z: " << b->z << "\n" << std::endl;

    delete a;
    delete b;

}
Deev
  • 471
  • 2
  • 9
  • This works but I don't understand how. How is the compiler able to figure out N? – Gary Fisher Feb 16 '22 at 23:46
  • @GaryFisher: It works because the type of `Aargs` is `int[2]` and `Bargs` is `int[3]`. If the compiler cannot deduce the size at compile time for whatever reason, you can always accept a pointer `U* a`, but you would still need to specify the `size_t N` as a template parameter to use `std::make_index_sequence{}`. – Deev Feb 21 '22 at 04:14