is it possible to create pointers of derived classes using their base class constructor without modifying the layout of the derived classes?

Viewed 87

consider this layout:

#include <memory>
#include <vector>

struct data{};

struct task {
    data* data_ptr = nullptr;

    virtual void work() = 0;
};

struct special_task : task {

    void work() override { /*work with the data*/ }
};

a task has access to data. The derived classes must implement their indivial work() routine.

Now task_collection stores the data and a vector of task pointers, and instances of derived classes can be added to that vector:

struct task_collection {
    data data;
    std::vector<std::unique_ptr<task>> tasks;

    template<typename T>
    void add() {
        this->tasks.push_back(std::make_unique<T>());
        this->tasks.back()->data_ptr = &this->data;
    }
};

int main() {
    task_collection t;
    t.add<special_task>();
}

now this works very well. However, I am wondering if I can replace the data* data_ptr with a reference as it seems more appropriate here and it also replaces the many -> with .s


However, it seems impossible to implement this without changing the layout of special_task as the reference requires a constructor in the base task class and derived classes dismiss their base constructors:

struct task {
    task(data& data) : data_ref(data){}
    data& data_ref;

    virtual void work() = 0;
};

struct special_task : task {

    void work() override { /*work with the data*/ }
};

struct task_collection {
    data data;
    std::vector<std::unique_ptr<task>> tasks;

    template<typename T>
    void add() {
        this->tasks.push_back(std::make_unique<T>(this->data)); //Error!
    }
};

It gives this error:

Error   C2664   'special_task::special_task(const special_task &)': cannot convert argument 1 from 'data' to 'const special_task &'

I am aware that this can be "solved" by adding

using task::task;

to every derived class, as now it now finds the appropriate constructor. This is not really a solution though, as there are hundreds of derived classes potentially, written by multiple people. If one instance of using task::task; is missing it could cause headaches. The additional lines of code also negate the benefit that comes from using reference instead of pointers.


So is there any way to implement task::data_ref as a reference where only task_collection or task are modified, but not any of the derived classes?

1 Answers

ITNOA

If owner of question can accept some of time using get() instead of . to access to the data, he can use std::reference_wrapper instead of raw reference to resolve rebinding problem.

#include <memory>
#include <vector>
#include <functional>

struct data{};

static data empty_data;

struct task {
    std::reference_wrapper<data> data_ptr = empty_data;

    virtual void work() = 0;
};

struct special_task : task {

    void work() override { /*work with the data*/ }
};

struct task_collection {
    data data;
    std::vector<std::unique_ptr<task>> tasks;

    template<typename T>
    void add() {
        this->tasks.push_back(std::make_unique<T>());
        this->tasks.back()->data_ptr = std::ref(this->data);
    }
};

int main()
{
    task_collection t;
    t.add<special_task>();
}

For using the data, you can for example behave like below

data temp_data = t.tasks.back()->data_ptr;

As you can see in How to correctly use std::reference_wrappers, Class std::reference_wrapper implements an implicit converting operator to T&:

constexpr operator T& () const noexcept;

So the implicit operator is called when a T (or T&) is required, and you do not need using get() function, For instance

void f(some_type x);
// ...
std::reference_wrapper<some_type> x;
some_type y = x; // the implicit operator is called
f(x);            // the implicit operator is called and the result goes to f.

So you have to sometimes to using .get(). instead of always using ->

Related