0

I am writing an abstract CRTP-based abstract base class for static/dynamic arrays. I intend to put as many methods as possible in the base class so that there is no code duplication in the derived classes. I have got the indexing operator working but I'm struggling with the assignment (=) operator.

/** Array base class. */
template <class t_derived_class, class data_type>
class ArrayBase
{
  private:
  t_derived_class& Derived;

  public:
  /** Default constructor. */
  ArrayBase() : Derived(static_cast<t_derived_class&>(*this)) {}

  /** Pure virtual desctructor. */
  virtual ~ArrayBase() {};

  /** Index operator overloads. */
  data_type& operator[](const std::size_t _index)
  {
    return *(Derived.begin() + _index);
  }

  const data_type& operator[](const std::size_t _index) const
  {
    return *(Derived.begin() + _index);
  }

  /** Assignment operator overloads. */
  t_derived_class& operator=(const t_derived_class& _other_derived)
  {
    for(std::size_t i = 0; i < Derived.size(); ++i) Derived[i] = _other_derived[i];
    return Derived;
  }
};
/** Static array class. */
template <class data_type, int array_size>
class StaticArray : public std::array<data_type, array_size>, public ArrayBase<StaticArray<data_type, array_size>, data_type>
{
  using Base = ArrayBase<StaticArray<data_type, array_size>, data_type>;
  friend Base;

  public:
  /** Default constructor. */
  StaticArray() : std::array<data_type, array_size>() {}

  /** Default destructor. */
  ~StaticArray() = default;

  using Base::operator[];
  using Base::operator=;
};

/** Dynamic array class. */
template <class data_type>
class DynamicArray : public std::vector<data_type>, public ArrayBase<DynamicArray<data_type>, data_type>
{
  // ...
};
int main()
{
  StaticArray<double, 3> array1;
  array1[0] = 1.0;
  array1[1] = 2.0;
  array1[2] = 3.0;

  // This code compiles
  StaticArray<double, 3> array2 = array1;

  // This code does not compile
  array2 = array1; 

  return 0;
}

My IDE (CLion) gives me the following error when I use the assignment operator as above:

Object of type 'StaticArray<Apeiron::Float, 3>' (aka 'StaticArray<double, 3>') cannot be assigned because its copy assignment operator is implicitly deleted

The compiler errors are:

error: ‘Apeiron::StaticArray<double, 3>& Apeiron::StaticArray<double, 3>::operator=(const Apeiron::StaticArray<double, 3>&)’ cannot be overloaded with ‘t_derived_class& Apeiron::ArrayBase<t_derived_class, data_type>::operator=(const t_derived_class&) [with t_derived_class = Apeiron::StaticArray<double, 3>; data_type = double]’
   90 | class StaticArray : public std::array<data_type, array_size>, public ArrayBase<StaticArray<data_type, array_size>, data_type>
      |       ^~~~~~~~~~~
Array.h:62:20: note: previous declaration ‘t_derived_class& Apeiron::ArrayBase<t_derived_class, data_type>::operator=(const t_derived_class&) [with t_derived_class = Apeiron::StaticArray<double, 3>; data_type = double]’
   62 |   t_derived_class& operator=(const t_derived_class& _other_derived)

Can anyone advise me on how I can get this to work?

niran90
  • 248
  • 1
  • 10
  • 1
    Note that your assignment will misbehave miserably with `DynamicArray` of different sizes. – molbdnilo Oct 12 '21 at 09:39
  • @molbdnilo Can you elaborate? I have not included the size checking methods that I've implemented in both derived classes, but isn't that the only issue? And then of course checking that the size of the static arrays are the same. – niran90 Oct 12 '21 at 09:41
  • 1
    Do you need to define `operator=`, doesn't the default one (in Derived) does the job? – Jarod42 Oct 12 '21 at 09:46
  • @Jarod42 In fact the assignment operator that I actually want to define is for assigning a single value of type `data_type` to all array entries. I just happened to start off with the copy assignment operator. But I agree, I should be able to use the pre-defined copy assignment operator in `std::array` and `std::vector`. – niran90 Oct 12 '21 at 09:48

1 Answers1

2

Since the member variables of your ArrayBase are reference, the implicitly-declared ArrayBase::operator= will be automatically deleted.

The alternative is to remove the member variables and directly use the help function to get the reference of the derived class:

template <class t_derived_class, class data_type>
class ArrayBase
{
  private:
  t_derived_class& Derived() noexcept {
    return static_cast<t_derived_class&>(*this);
  };

  const t_derived_class& Derived() const noexcept {
    return static_cast<const t_derived_class&>(*this);
  };

  public:
  /** Pure virtual desctructor. */
  virtual ~ArrayBase() {};

  /** Index operator overloads. */
  data_type& operator[](const std::size_t _index)
  {
    return *(Derived().begin() + _index);
  }

  // ...
};
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • gcc still rejects the code [Demo](https://godbolt.org/z/6K3W6f6z7) with error: *"'constexpr StaticArray& StaticArray::operator=(const StaticArray&)' cannot be overloaded with 't_derived_class& ArrayBase::operator=(const t_derived_class&) [with t_derived_class = StaticArray; data_type = float]'"*. And as other compilers pass with the typo `return Derived;` instead of `return Derived();`, I suspect it doesn't bahave as expected. – Jarod42 Oct 12 '21 at 09:42
  • Derived classes should remove `using Base::operator=;`. [Demo.](https://godbolt.org/z/enbYWsazr) – 康桓瑋 Oct 12 '21 at 09:46
  • But as I also said, typo `return Derived;` is not spotted, so that method is not instantiated, and could be removed... (it happens that default `Derived::operator=` does already the job). – Jarod42 Oct 12 '21 at 09:49
  • You are right, the user-defined `operator=` is not instantiated here. – 康桓瑋 Oct 12 '21 at 09:53