Search code examples
c++templatesinheritanceshared-ptrstdvector

Function template to populate a vector of shared_ptr to Base & Derived objects


Consider the following:

#include <memory>
#include <utility>
#include <vector>

class Base
{
public:
    Base()
        : x(0)
    {}
    int x;
    virtual ~Base() = default;
};

class Derived : public Base
{
public:
    Derived(double z0)
        : Base{}
        , z{ z0 }
    {}
    double z;
};

template<class T> // T might be either a Base or a Derived class.
std::vector<std::shared_ptr<Base>> MakeVector(std::size_t numElements)
{
    std::vector<std::shared_ptr<Base>> vec;
    for(auto &i : numElements) {     // Compiler error occurs here.
        vec.push_back(std::make_shared<T>());
    }
    return vec;
}

class Foo
{
public:
    Foo(std::size_t num_elements,
        std::vector<std::shared_ptr<Base>> bars = {})
    : m_bars{bars.empty() ? MakeVector<Base>(num_elements) : std::move(bars)}
    {}

    std::vector<std::shared_ptr<Base>> m_bars;
};

int main()
{
    const std::size_t foo1Size = 4;
    const std::size_t foo2Size = 5;

    // Create a vector of shared_ptr to 4 Base objects:
    Foo foo1 {foo1Size};

    // Create a vector of shared_ptr to 5 Derived objects:
    Foo foo2 {foo2Size, MakeVector<Derived>(foo2Size)};
}

The objective here is to create a requested number of Base or Derived objects, and populate a std::vector with shared_ptrs to those objects.

I am getting a compiler error on the for statement:

error: there are no arguments to ‘begin’ that depend on a template parameter, so a declaration of ‘begin’ must be available [-fpermissive]

If I use a std::iterator with begin and end, unfortunately the iterator becomes invalid with each push_back. And I need to iterate a specific number of times, anyway, to populate the vector.

Is there an obvious solution to this problem?


Solution

    1. You can't use range-based for loop on an std::size_t. You could change

      for(auto &i : numElements) {
      

      to

      for (std::size_t i = 0; i < numElements; i++) { 
      
    2. Derived doesn't have default constructor; you need to pass the argument to it for constructing, otherwise std::make_shared<T>() would fail. You can change MakeVector to the following with parameter pack:

      template<class T, class... Types> // T might be either a Base or a Derived class.
      std::vector<std::shared_ptr<Base>> MakeVector(std::size_t numElements, Types... args)
      {
          std::vector<std::shared_ptr<Base>> vec;
          for (std::size_t i = 0; i < numElements; i++) {
              vec.push_back(std::make_shared<T>(args...));
          }
          return vec;
      }
      

      then use it like

      // Create a vector of shared_ptr to 5 Derived objects:
      Foo foo2 {foo2Size, MakeVector<Derived>(foo2Size, 42)};