Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally defining the constructor of a template class, based on the template argument

Tags:

c++

templates

I have the follow class, used as a generic "Point/vector in 2D/3D/etc space":

template<size_t dimension>
class Vector
{
private:
    std:array<float, dimension> data;
public:
    //common vector operations etc
    inline float magnitude() const;
    inline Vector<dimension> normalized() const;
    inline static float dotProduct(const Vector<dimension>& left, const Vector<dimension>& right);

    //vector arithmetic operators etc
    inline Vector<dimension> &operator+=(const Vector<dimension> &other);
    inline Vector<dimension> &operator*=(float s);
}

There are way more operators and such on this class, but I omitted most of them for brevity. My question is, how can I define the constructor for this class?

When dimension is 2, I want a constructor that takes 2 arguments:

Vector<2>::Vector(float x, float y) : data({x,y}) {}

When dimension is 3, I want a constructor that takes 3 arguments:

Vector<3>::Vector(float x, float y, float z) : data({x,y,z}) {}

By design, this class supports arbitrary dimensions, so creating a specialization for each dimension isn't an attractive approach, neither is defining a SFINAE-enabled constructor for each supported dimension. How can I write a single constructor for Vector<N> that takes in N arguments and passes them as an initializer list to the data array?

like image 938
Elliott Avatar asked Oct 28 '25 10:10

Elliott


2 Answers

Is this what you are looking for?

template <typename ...Args,
    typename std::enable_if_t<dimension == sizeof...(Args), int> = 0>
    Vector(Args&& ...args) : data { std::forward<Args>(args)... }
{ }

template <typename ...Args,
    typename std::enable_if_t<dimension != sizeof...(Args), int> = 0>
    Vector(Args&& ...args)
{
    static_assert(sizeof...(Args) == dimension, "Dimension doesn't match parameter count");
}
like image 199
Jts Avatar answered Oct 31 '25 00:10

Jts


Both Clang and MSVC are, or will soon be, implementing std::make_integer_sequence with an intrinsic, and GCC/libstdc++ now has a O(log N) library-based implementation, so let's not reinvent the wheel of "how do I get a pack of a particular size". Build an index sequence, and generate the desired list of types by pack expansion with an alias template.

template<size_t dimension,
         class IndexSeq = std::make_index_sequence<dimension>>
class VectorStorage;

template<size_t dimension, size_t... Is>
class VectorStorage<dimension, std::index_sequence<Is...>> {
    template<size_t> using ElementType = float;
protected:
    std:array<float, dimension> data;
public:
    VectorStorage() = default;
    VectorStorage(ElementType<Is>... args) : data{args...} {}
 };

template<size_t dimension>
class Vector : VectorStorage<dimension>
{
public:
    using VectorStorage<dimension>::VectorStorage;
    // other stuff

};
like image 35
T.C. Avatar answered Oct 31 '25 02:10

T.C.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!