Using variable number of float arguments

Viewed 1212

I'm trying to implement a flexible constructor for my struct Polynomial :

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

The degree of the polynomial is variable. What I would like is to have a constructor like

Polynomial(float... _coefficients);

I've tried variadic template :

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

But float is a non-type, so I've done :

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

But this allow my coefficients to be anything, not realy what I want. I know I could use :

Polynomial(size_t _degree, ...);

But it is really unsafe.

At the moment I'm using :

Polynomial(std::vector<float>);

But this force the call to be like :

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

So I would like to know if there is a good way to do this.

Thank you !

6 Answers

You can use 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. };
}

Answering your question

I would like to know if there is a good way to do this

Yes, i think the way you are doing it it's more than acceptable. And even the syntax where you use it Polynomial P({f1, f2, f3}); isn't so ugly at all.

Furthermore, use std::vector is as efficient as variadic and much more comprehensible for others.

With the variadic approach, you will find difficult to force those types received to be float, but with std::vector you got it controlled

I think that your way (a vector parameter, or better (IMHO) an initializer list) is a good way.

Another way (simple but with drawbacks) could be the use of narrowing to be sure that Args... are float or types that can be narrowed to float. Something like

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

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

It's simple and works, by example

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

but your constructor doesn't accept, by example, integer or double or long double values; so

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

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

and probably is too restrictive.

You can use a recursive template parameter processing. The general idea is to use a private method that adds first parameter to the coefficient vectors and recurse with other parameters until they have all been processed:

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...);
    }
};

You have many options. You might want to look at the constructors of std::vector for inspiration. For example a templated constructor that takes two iterators is very flexible:

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()};

Or you could take a std::initializer_list<float> as Jodocus suggested.

You could have a templated constructor that takes any container type:

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};

Live demo.

Or you could provide a combination of different constructors to suit different needs.

The way you are doing it is a "good way". Imagine the caller wants to pass 100 coefficients. If you use variadics you force the caller to do something like that:

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

If I was to use 100 coefficients I would certainly store them in a vector, and it would be rather cumbersome to pass them to your polynomial:

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

If however, you accept a vector, the caller can do both without pain:

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

On the other hand, using std::vector but not allowing a different container is an arbitrary restriction. The better way then would be to accept iterators, and let the caller do:

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