简体   繁体   中英

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.

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

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

This is a bit off-topic, but perhaps it'll be the least amount of work: Use a tuple . Then you get all the accessor functions for free.

The only thing left to do is to make a tuple factory:

template <typename T, unsigned int D> struct tuple_maker
{
  typedef typename tcat<T, tuple_maker<T, D - 1>::type>::type type;
}
template <typename T> struct tuple_maker<T, 1>
{
  typedef std::tuple<T> type;
}

We need the auxiliary tcat :

template <typename T, typename Tuple> struct tcat;
template <typename T, typename ...Args> struct tcat<T, std::tuple<Args...>>
{
  typedef typename std::tuple<T, Args...> type;
}

Usage:

tuple_maker<float, 3>::type myVec3D;

With template aliases we can do one better:

template <typename T, unsigned int D>
using MyVector = typename tuple_maker<T, D>::type;

MyVector<double, 4> myVec4D;

This should do the job:

template<int N, typename T>
class Array
{
private:
    T Data[N];

public:
    template<int i>
    void Init() { }
    template<int i, typename... Args>
    void Init(T Arg0, Args... Rest)
    {
        Data[i] = Arg0;
        Init<i + 1>(Rest...);
    }
    template<typename... Args>
    Array(T Arg0, Args... Rest)
    {
        static_assert(sizeof...(Args) + 1 == 5, "Wrong number of arguments");
        Data[0] = Arg0;
        Init<1>(Rest...);
    }
};

int main (int argc, const char * argv[])
{
    Array<5, int> arr(1, 2, 3, 4, 5);
    return 0;
}

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

You can't do that. You can specialize for each of the supported dimensions and provide the appropiate constructor for each one. Or you can define a constructor that takes multiple default-valued arguments and ignore those that are not used. Or you can define multiple constructors, from 1 to some upper limit, and static_assert if the number of arguments is bigger than D .

How to add a function only if D if a specific number?

That is done with specialization. You would have to move all the common functionality to some VectorBase template and inherit from it, and do a partial specialization on the dimensionality to add those functions.

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

Or you could define all those functions for the base template, and static_assert if D is not enough to be using that function. Now you have just lost explicit instantiation though. You could also add a dummy template parameter to such functions, so you can use enable_if and SFINAE to discard functions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM