Использование переменной числа аргументов float

Я пытаюсь реализовать гибкий конструктор для моей структуры Polynomial:

struct Polynomial
{
    std::vector<float> coefficients;
    size_t degree;
};

Степень полинома является переменной. Я хотел бы иметь конструктор типа

Polynomial(float... _coefficients);

Я пробовал вариационный шаблон:

template<float... Args>
Polynomial(Args... args);

Но float не является типом, поэтому я сделал:

template<typename... Args>
Polynomial(Args... args);

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

Polynomial(size_t _degree, ...);

Но это действительно опасно.

В данный момент я использую:

Polynomial(std::vector<float>);

Но это заставляет вызов выглядеть как:

Polynomial P({f1, f2, f3}); // with fn floats

Итак, я хотел бы знать, есть ли хороший способ сделать это.

Спасибо!

Ответ 1

Я думаю, что ваш путь (векторный параметр или лучше (IMHO) список инициализаторов) является хорошим способом.

Другим способом (простым, но с недостатками) может быть использование сужения, чтобы быть уверенным, что Args... float или типы, которые можно сузить до float. Что-то вроде

struct Polinomial
 {
   std::vector<double>  v;
   std::size_t          degree;

   template <typename ... Args>
   Polinomial (Args const & ... args)
      : v { float{args}... }, degree { sizeof...(Args) }
    { }
 };

Это просто и работает, на примере

Polinomial p { 2.3f, 3.5f, 6.7f };

но ваш конструктор не принимает, например, значения integer или double или long double; так

Polinomial p { 2.3f, 3.5f, 6.7 };
// ........................^^^  double, error

Polinomial p { 2.3f, 3.5f, 6 };
// ........................^  int, error

и, вероятно, слишком ограничительный.

Ответ 2

Вы можете использовать initializer_list:

#include <vector>
#include <initializer_list>

struct Polynomial {
    std::vector<float> coeffs;
    std::size_t degree;

    Polynomial(std::initializer_list<float> init)
        : coeffs{ init }, degree(init.size()) { }
};

int main() {
    Polynomial p{ 1, 2, 3. };
}

Ответ 3

Отвечая на ваш вопрос

Я хотел бы знать, есть ли хороший способ сделать это

Да, я думаю, что вы делаете это более чем приемлемо. И даже синтаксис, в котором вы его используете, Polynomial P({f1, f2, f3}); не так уродлив.

Кроме того, использование std::vector столь же эффективно, как и вариационное и гораздо более понятное для других.

С помощью вариационного подхода вам трудно заставить те типы, которые были получены float, но с std::vector вы получили контроль над ним

Ответ 4

Вы можете использовать рекурсивную обработку параметров шаблона. Общая идея заключается в использовании частного метода, который добавляет первый параметр в векторы coefficient и рекурсирует с другими параметрами до тех пор, пока все они не будут обработаны:

struct Polynomial
{
    template<class...Args>
    Polynomial(Args... coeffs) {
        init(coeffs...);
        degree = coefficients.size() - 1;
    }
    std::vector<float> coefficients;
    size_t degree;
private:
    void init(float coeff) {
        coefficients.push_back(coeff);
    }
    template<class...Args> void init(float first, Args...others) {
        init(first);
        init(others...);
    }
};

Ответ 5

У вас много вариантов. Вы можете посмотреть на конструкторы std::vector для вдохновения. Например, шаблонный конструктор, который принимает два итератора, очень гибкий:

template<typename T>
Polynomial(T begin, T end) : coefficients(begin, end), degree(coefficients.size()) {}

auto init = std::vector<float>{2.0, 1.0, 4.0};
Polynomial p2{init.begin(), init.end()};

Или вы можете взять std::initializer_list<float>, как предположил Джодокус.

У вас может быть шаблонный конструктор, который принимает любой тип контейнера:

template<typename T>
Polynomial(T container) 
: coefficients(begin(container), end(container))
, degree(coefficients.size()) {}

auto init = std::vector<float>{2.0, 1.0, 4.0};
Polynomial p2{init};

Живая демонстрация.

Или вы можете предоставить комбинацию различных конструкторов для удовлетворения различных потребностей.

Ответ 6

То, как вы это делаете, - "хороший способ". Представьте, что вызывающий абонент хочет передать 100 коэффициентов. Если вы используете переменные, вы вынуждаете вызывающего абонента делать что-то вроде этого:

float c1,c2,c3,....c100;
// init them
Polynomial P(c1, c2, c3,....,c100);

Если бы я использовал 100 коэффициентов, я бы, конечно, сохранил их в векторе, и было бы довольно громоздким передать их вашему полиному:

auto coeffs = std::vector<float>(100);
Polynomial P(coeffs[0],coeffs[1],....,coeffs[100]);

Если, однако, вы принимаете вектор, вызывающий может обойтись без боли:

Polynomial P({c1,c2,c2});
Polynomial P(coeffs);

С другой стороны, использование std::vector, но не допускающее использование другого контейнера, является произвольным ограничением. Лучше было бы принять итераторы и позволить вызывающему:

Polynomial P(coeffs.begin(),coeffs.end());