Передача в конструктор класса неограниченное количество аргументов

возникла проблема с тем, чтобы передать в конструктор класса заранее неизвестное количество аргументов. Я нашёл два возможных варианта решения

  1. (менее надёжный) это создать указатель на последний аргумент и использовать инкременты, так как обычно аргументы лежат в памяти друг за другом, но метод, кажется мне не надёжным и появляется проблема отличить последний введённый аргумент от мусора
Vector(int arg, ...)
{
    int *p = &arg;
}
  1. Использовать стандартную библиотеку stdarg.h, что будет явно адекватнее, но я столкнулся с проблемой: не могу отличить, когда заканчиваются аргументы и начинается мусор:
#include <stdarg.h>

class Vector
{
public:
    Vector(int arg, ...)
    {
        va_list ap;
        va_start(ap, arg);

        std::cout << arg << std::endl;

        for (int i = 0; i = va_arg(ap, int);)
        {
            std::cout << i << std::endl;
        }

        va_end(ap);
    }

};

int main()
{
    Vector a(1, 2, 3);
    return 0;
}

Вот что он выводит

1
2
3
12914723
12914723
16404480
14088232
2058353509
14088244
2058350259
1000937776
31
14088272
14088272
2058359868
14088272
2058350259
1000937776
31
14088300
14088300
2058359868
12914723
19291320
2001875552
14088312
2058350259
1000937776

Есть разные костыли, например первый аргумент использовать как количество последующих аргументов, например

Vector a(3, 1, 2, 3);

Но я бы не был программистом, если бы не ленился каждый раз думать о том, сколько там будет параметров и что случится, если я осчетаюсь. Я придумал как можно это обойти, например использовать препроцессор по типу:

#define a(...) a(__VA_ARGS__, NULL)

На данном примере работает хорошо, но я так и не догадался как написать его, чтобы он работал для всех переменных, а не только для "а".

Буду очень благодарен за подсказку или за идею как обойти этот недочет. Заранее спасибо!


Ответы (2 шт):

Автор решения: HolyBlackCat

(1) вызывает неопределенное поведение. (2) требует отдельно передавать количество элементов, и никак не защищает вас от передачи неправильного типа.

Забудьте об <cstdarg>, и тем более об (1).

Для контейнеров правильно писать так:

Vector(std::initializer_list<int> list)
{
    for (int x : list)
        stdd::cout << x << '\n';
}

Да, Vector a(1, 2, 3); работать не будет, зато Vector a = {1, 2, 3}; будет. Так же, как у стандартных контейнеров.

Можно заставить работать синтаксис Vector a(1, 2, 3);, но это хуже, потому что конструктор теперь обязан быть шаблоном:

#include <iostream>

struct Vector
{
    template <typename ...P>
    Vector(P &&... params)
    {
        ([&]{
            std::cout << params << '\n';
            // Если вы собрались куда-то копировать/перемещать аргумент, то `std::forward<P>(params)`.
        }(), ...);
    }
};

int main()
{
    Vector a(1, 2, 3);
}

Тут сложность в том, чтобы запретить передавать аргументы неправильного типа. Я бы сделал так:

#include <concepts>
#include <iostream>

struct Vector
{
    template <std::convertible_to<int> ...P>
    Vector(P &&... params)
    {
        ([&]{
            int x(std::forward<P>(params));
            std::cout << x << '\n';
        }(), ...);
    }
};

int main()
{
    Vector a(1, 2, 3);
}

я хотел бы использовать этот класс в будущем в работе с математическими исчислениями

Вам точно нужны вектора переменного размера? Если вектора нужны только маленькие, то обычно их шаблонируют размером, что-то типа: template <typename T, std::size_t Size> struct Vector.

→ Ссылка
Автор решения: AlexGlebe

Я бы не стал загружать себя сложностями, и передавал бы просто список аргументов. Переменное количество аргументов - это не очень стандартно. Если бы была важна скорость, то конечно можно было-бы массив с аргументом длины.

# include   <iostream>
# include   <list>
class Vector
{
public:
    Vector(std::list<int> const & l )
    {

        for (int i : l)
        {
            std::cout << i << std::endl;
        }

    }

};

int main()
{
    Vector a({1, 2, 3});
}
→ Ссылка