Search code examples
c++templatesc++14variadic-templates

My variadic templated constructor hides copy constructor, preventing the class to be copied


I made a Vector<numType, numberOfCoords> class. I was happy with it, it's kinda weird but it seemed to work at the start. But I just found out copying the vectors is impossible.

The reason is that to allow the number of coordinates to be a template, there is a templated variadic constructor which expects the right number of coordinates.

This is what it looks like, with the utility math methods removed:

template <typename NumType, unsigned char Size>
class Vector
{
public:
  using CoordType = NumType;

  //Vector(const Vector& v) : values(v.values) {}
  //Vector(Vector&& v) : values(std::move(v.values)) {}

  template<typename... NumTypes>
  constexpr Vector(NumTypes&&... vals) : values{ std::forward<NumTypes>(vals)... }
  {
    static_assert(sizeof...(NumTypes) == Size, "You must provide N arguments.");
  }

  Vector(const std::array<NumType, Size>& values) : values(values) {}
  Vector(std::array<NumType, Size>&& values) : values(std::move(values)) {}

  const NumType& operator[](size_t offset) const { return values[offset]; }
  NumType& operator[](size_t offset) { return values[offset]; }
  //Vector& operator=(const Vector& other) { values = other.values; }
  //Vector& operator=(Vector&& other) { values = std::move(other.values); }

  std::array<NumType, Size> values;

};

As you can see - now commented out - I did try to implement copy and move and assignment operations manually. It had no effect, the error is always along the lines of:

cannot convert ‘Vector<int, 3>’ to ‘int’ in initialization

This is because it is still trying to use the variadic constructor, instead of the copy constructor.

Here is a full working broken sample: https://ideone.com/Jz86vP


Solution

  • You might:

    • SFINAE your variadic constructor, for example:

      template <typename... NumTypes,
                std::enable_if_t<sizeof...(NumTypes) == Size
                             && std::conjunction<std::is_same<NumType, NumTypes>::value,
                                int> = 0>
      constexpr Vector(NumTypes&&... vals) : values{ std::forward<NumTypes>(vals)... }
      {
      }
      

      Note: std::conjunction is C++17, but can be done in C++14.

    • or prepend a tag:

      template <typename... NumTypes>
      constexpr Vector(struct SomeTag, NumTypes&&... vals) :
          values{ std::forward<NumTypes>(vals)... }
      {
      }
      
    • or redesign the class, for example, something like:

      template <typename T, std::size_t>
      using always_type = T;
      
      template <typename NumType, typename Seq> class VectorImpl;
      
      template <typename NumType, std::size_t ... Is>
      class VectorImpl<NumType, std::index_sequence<Is...>>
      {
      public:
        using CoordType = NumType;
      
        VectorImpl(const VectorImpl&) = default;
        VectorImpl(VectorImpl&&) = default;
        VectorImpl& operator=(const VectorImpl&) = default;
        VectorImpl& operator=(VectorImpl&&) = default;
      
        constexpr VectorImpl(always_type<NumType, Is>... vals) : values{ std::forward<NumType>(vals)... }
        {
        }
      
        VectorImpl(const std::array<NumType, sizeof...(Is)>& values) : values(values) {}
        VectorImpl(std::array<NumType, sizeof...(Is)>&& values) : values(std::move(values)) {}
      
        const NumType& operator[](size_t offset) const { return values[offset]; }
        NumType& operator[](size_t offset) { return values[offset]; }
      
        std::array<NumType, sizeof...(Is)> values;
      };
      
      template <typename T, std::size_t N>
      using Vector = VectorImpl<T, std::make_index_sequence<N>>;
      

      Demo