2

I'm trying to initialize a private member array of a class without using the STL (because it is not supported on the Arduino microcontroller platform I'm using). This means no std::array or std::initializer_list etc.

The following compiles correctly using gcc 5.4.0 and avr-gcc 4.9.2, but that seems to be a bug. Clang throws an error saying error: array initializer must be an initializer list (as expected).

Code

#include <iostream>
#define PRINTFN() std::cout << __PRETTY_FUNCTION__ << std::endl

class Object {
  public:
    Object(int number) : number(number) { PRINTFN(); }
    Object(const Object &o) : number(o.number) { PRINTFN(); }
    void print() { std::cout << "The number is " << number << std::endl; }

  private:
    const int number;
};

template <size_t N>
class ManyObjects {
  public:
    ManyObjects(const Object(&objects)[N]) : objects(objects) {}
    void print() {
    for (Object &object : objects)
        object.print();
    }

  private:
    Object objects[N];
};

int main() {
    ManyObjects<3> many = {{1, 2, 3}};
    many.print();
}

Output

Object::Object(int)
Object::Object(int)
Object::Object(int)
Object::Object(const Object&)
Object::Object(const Object&)
Object::Object(const Object&)
The number is 1
The number is 2
The number is 3

What is the proper way to initialize objects? Or is it just not possible with the given constraints?

Passer By
  • 19,325
  • 6
  • 49
  • 96
tttapa
  • 1,397
  • 12
  • 26

3 Answers3

1

You can use variadic templates:

In ManyObjects class:

template <typename... _Args>
ManyObjects(_Args&&... arguments) :
    objects { arguments... }
{
}

More here

acade
  • 245
  • 2
  • 7
  • This does indeed work, but it's less readable, and less friendly to the end user (the Arduino platform is aimed at beginners), because it doesn't separate the arguments of the list from any other parameters to the `ManyObjects` constructor. I guess I could wrap the class that stores the objects with my original class, but I'm not entirely convinced. – tttapa Jul 19 '18 at 12:09
  • @tttapa, `template ManyObjects (int arg1, char arg2, _Args&&... arguments)` - if you want add some other parameters to the ManyObjects constructor. – acade Jul 19 '18 at 12:18
  • Care with variadic template constructor, as `ManyObjects(ManyObjects&)` would match the template instead of the copy constructor. – Jarod42 Jul 19 '18 at 12:40
0

Yes variadic templates work but it is a bit tricky :

template <size_t N>
class ManyObjects {
  public:
    template<typename T, typename ...Args>
    ManyObjects(const T& x, Args&&... args) : objects{x, args...}{}

  private:
    Object objects[N];
};

int main() {
    ManyObjects<3> many{1, 2, 3};
    ManyObjects<3> copymany{many};
    copymany.print();
}

For any fixed N it can be interpreted as :

template <size_t N=3>
class ManyObjects {
  public:
        ManyObjects(int x, int y, int z) : objects{x, y, z}{} 
...
};

What is at play here :

  1. Object cannot be default initialized due to the definition of the constructor Object(int)
  2. Object assignment operator is implicitly deleted because number is const
  3. Thus any array Object arr[N] must be explicitly initialized using an aggregate initialization.
  4. The only way I think of is to perform extended initialization via the variadic templates.
  5. To prevent matching the copy constructor you can specify the first argument outside the parameter pack. You loose the construction of size 0, which can be enabled with a template specialization.

     ManyObjects<0> noneof;
     noneof.print();
    
UmNyobe
  • 22,539
  • 9
  • 61
  • 90
  • Care with variadic template constructor, as `ManyObjects(ManyObjects&)` would match the template instead of the copy constructor. – Jarod42 Jul 19 '18 at 12:39
  • how would define the copy constructor ? – UmNyobe Jul 19 '18 at 12:45
  • In fact the template variadic constructor should be SFINAE to exclude signature matching the copy/move constructor one: checking size and first argument type. – Jarod42 Jul 19 '18 at 12:50
0

I ended up following the advice of VTT, and creating my own array wrapper.

I'd love to hear some feedback if there are things that I have to look out for, or possible bugs, etc.

#include <iostream>

class Object {
public:
  Object(int number) : number{number} {}
  void print() { std::cout << "The number is " << number << std::endl; }

private:
  const int number;
};

// -------------------------------------------------------------------------- //

template <class T, size_t N> class ArrayWrapper {
public:
    T &operator[](size_t index) { return data[index]; }
    const T &operator[](size_t index) const { return data[index]; }
    T *begin() { return &data[0]; }
    const T *begin() const { return &data[0]; }
    T *end() { return &data[N]; }
    const T *end() const { return &data[N]; }

  T data[N];
};

// -------------------------------------------------------------------------- //

template <size_t N> class ManyObjects {
public:
  ManyObjects(const ArrayWrapper<Object, N> &objects, const char *name)
      : objects{objects}, name{name} {}
  void print() {
    std::cout << name << std::endl;
    for (auto &object : objects)
      object.print();
  }

private:
  ArrayWrapper<Object, N> objects;
  const char *name;
};

// -------------------------------------------------------------------------- //

int main() {
    ManyObjects<3> many = {{1, 2, 3}, "Many"};
    many.print();
}
tttapa
  • 1,397
  • 12
  • 26