Question

I have a template class that replaces direct construction of objects with just storing them for future purpose.

template<typename T>
typedef struct Construction{
     int arg;
} Construction;

Now I want to pass a std::vector< Construction< Base > > to a function that should then call the constructors of every Element with the given argument.

This works when I only use the base type, but as soon as I try to mix base and derived types, problems emerge:

Construction<Base> construct = Construction<Derived>(5);

This won't work, while a regular assignment like Base& b = Derived(5); does.

Why does this not work?

Was it helpful?

Solution

This sounds like a lazy factory pattern. As you say in a comment, Constr<Base> does not hold enough information to construct a Derived. So when storing in a vector, you should use pointers rather than values. If you are willing to use the free store for the final objects, one solution is the following:

struct Base { int x; };
struct Derived : public Base { Derived(int x) : Base{x} { } };

template<typename T>
struct Constr;

template<>
struct Constr<Base> {
    virtual std::unique_ptr<Base> make() const = 0;
};

template<typename T>
struct Constr : Constr<Base> {
    int arg;
    Constr(int arg) : arg{arg} { }
    std::unique_ptr<Base> make() const override
    {
        return std::make_unique<T>(arg);
    }
};

int main() {
    Constr<Derived> a{5}, b{2}, c{8};
    std::vector<Constr<Base>*> v{&a, &b, &c};

    // later...
    std::vector<std::unique_ptr<Base>> u;
    for (auto& i : v)
        u.push_back(i->make());
    for (auto& i : u)
        std::cout << i->x << " ";  // 5 2 8
}

For this to work, we now make Constr<Derived> derive Constr<Base>. So we can use pointers/references to such objects following the same rules as pointers/references to Base/Derived, without defining any special constructors.

Note the forward declaration of Constr: this is needed because specialization Const<Base> needs to be defined before the general template; in turn, this is needed because the latter implicitly instantiates the former (by deriving it).

I cannot think of a simple solution without the free store, though. The only possibility is to use a raw memory buffer, either as a data member of Constr<Base>, or passed as an argument to make(). But this needs some work and will always have an upper bound on the size of Derived. Or, we could automatically choose between stack/free store depending on object size; this is generic but needs even more work.

OTHER TIPS

It does not work, because instantiations of class templates are not related via a subclass-superclass relationship, even if the types, used in the instantiation are.

In many cases you can solve the problem, by providing a constructor template and/or assignment operator template to your original template, like:

struct Base {
    int x;
};

struct Derived : public Base {
    int y;
};

template<typename T>
struct Constr {
    T t;

    Constr() = default;

    template<typename U> Constr(const Constr<U> &a) : t(a.t) {}
};

int main() {
    Constr<Derived> a;
    Constr<Base> b = a;
    // Constr<Derived> c = b; // error, as it should
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top