Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

templates: how to control number of constructor args using template variable.

I'm trying to make a simple Vector class (math) this way:

template <int D, typename T = float>
class Vector
{
  T m[D];
  // ...
};

Where D is the number of dimensions. If it is two, the vector will store two values of type T.


How can I declare the constructor function to take D arguments of type T?

Vector<2> v(1.0f, -6.3f);

How to add a function only if D if a specific number? I wish to add GetX() if D is >= 1, GetY() if D is >= 2 and GetZ() if D is >= 3, but the following code should generate a compile-time error:

Vector<2> v(1.0f, -6.3f);
cout << v.GetZ() << endl;

How to generate a compile-time error if D is < 1?

I'm not following any specific standard, anything will work for me.

like image 391
Guilherme Bernal Avatar asked Nov 16 '11 20:11

Guilherme Bernal


People also ask

How do I restrict a template type in C++?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

How do you use template arguments in C++?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

What is the difference between typename and class in template?

There is no difference between using <typename T> OR <class T> ; i.e. it is a convention used by C++ programmers.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration.


2 Answers

So I provided a bit of a silly answer that people liked. But this is much easier than that :)

template <int D, typename T = float>
class v {
public:
    template <typename... Args>
    v(Args... args) : a{ T(args)... } {
        static_assert(sizeof...(Args) == D, "wrong number of arguments");
    }

private:
    T a[D];
};

You can use variadic templates and SFINAE to get a constructor with the right number of parameters.

Constructors don't have return values so we need to use SFINAE on one of the parameters. And to use variadic templates, we'll need to have the parameter pack at the end.

This means we need to use SFINAE on the first parameter.

Then this means the parameter pack after the first parameter needs to have one less parameter than the dimensions.

With this in hand, we can write:

template <int D, typename T>
class v {
public:
    template <typename... Tail>
    v(typename std::enable_if<sizeof...(Tail)+1 == D, T>::type head, Tail... tail)
    : a{ head, T(tail)... } {}

private:
    T a[D];
};

And now:

v<4, int> a(1,2,3,4); // ok!
v<4, int> b(1,2,3);   // error! no such constructor
like image 172
R. Martinho Fernandes Avatar answered Oct 23 '22 05:10

R. Martinho Fernandes


I don't have access to a C++11 compiler but maybe something like this could work?

#include <array>
#include <type_traits>

template <int D, typename T>
class Vector
{
    static_assert(D > 0, "Dimension must be greater than 0");
    std::array<T,D> m;
public:
    template<typename... Args>
    Vector(Args&&... args) : m{T(args)...}
    {
         static_assert(sizeof...(Args) == D, "Invalid number of constructor arguments.");
    }

    T GetX() const { return m[0]; }
    T GetY() const { return m[1]; }
    T GetZ() const { return m[2]; }
};

template <typename T>
class Vector<1, T>
{
    std::array<T,1> m;
public:
    Vector(const T& t0) : m{t0}
    {
    }

    T GetX() const { return m[0]; }
};

template <typename T>
class Vector<2, T>
{
    std::array<T,2> m;
public:
    Vector(const T& t0, const T& t1) : m{t0, t1}
    {
    }

    T GetX() const { return m[0]; }
    T GetY() const { return m[1]; }
};
like image 33
ronag Avatar answered Oct 23 '22 06:10

ronag