Search code examples
templatesc++17template-specialization

C++ template class specialization constructor


I want to create a generic Vector class like this Vector<int Dimension, typename Type>. And this is my code:

template <int Dimension, typename Type>
class Vector
{
public:
  Vector() 
    : _data {} 
  {}
  Vector(const std::array<Type, Dimension>& init)
    : _data {init}
  {}
  ...
protected:
 std::array<Type, Dimension> _data;
};

With that declaration, i can initialize my vector with a std::array.

Vector<3, float> vec({1.0f, 2.0f, 3.0f});
// or like this
Vector<3, float> vec = {{1.0f, 2.0f, 3.0f}};

But now i want my vector has a better constructor for some common vector type like Vec1, Vec2, Vec3, ... I have tried to create a recursion template class to add an additional constructor for each of them:

template <typename Type>
class Vector<3, Type>
{
public:
  Vector(const Type& x, const Type& y, const Type& z)
    : _data {x, y, z}
  {}
};

But when i compile my program i received this error:

error C3861: '_data': identifier not found

My questtion are:

  • Why this error happen?
  • And how can i add an additional constructor for some common template of my Vector.

PS: Sorry if my English is hard to understand 🤦‍♀️.


Solution

  • Why?

    There is no recursion here. Vector<3, Type> is a wholly independent thing, unrelated to the generic Vector<Dimension, Type>. It does not contain any _data or anything else that the main template contains.

    How?

    You don't need recursion or specialisation here, you need a forwarding constructor.

      template <int Dimension, typename Type>
      class Vector
      {
        public:
          template <typename ... T>
          Vector(T&& ... t) 
            : _data{std::forward<T>(t)...} {}
      
        protected:
          std::array<Type, Dimension> _data;
      };
    

    This is the only constructor you need. It basically says "initialise Vector with whatever you can initialise _data". So you can have:

    Vector<4, int> v4(1,2,3,4);
    Vector<2, int> v2{1,2};
    
    std::array<float, 3> u3 = {1.0, 2.0, 3.0};
    Vector<3, float> v3{u3};
    
    Vector<5, float> v5;
    

    Is this all?

    No. Perhaps you do need to add some functionality to Vector<3,Type> which is not present in the main template. In this case you can use inheritance.

    • Rename your Vector class template to VectorBase

    • Create a new Vector class template that inherits from ``VectorBase` and uses its constructor, and does nothing more:

      template <int Dimension, typename Type>
      class Vector : public VectorBase<Dimension, Type> {
         public:
             using VectorBase<Dimension, Type>::VectorBase;
      };
      
    • Now you can specialise Vector and add functionality to it. You need to repeat the using declaration in every specialisation, but that's not too much boilerplate.