Ivan Smirnov
Ivan Smirnov

Reputation: 4435

Setting a default constructor for one type with multiple inheritance

I have two classes, Base and Derived. Derived inherits all Base's constructors. Also, I have a template class Printer<T> which holds a reference to an object of type T and has a method print() which prints an object somehow. Here is a minimal illustration.

class Base {
public:
    Base(int x) : x(x) {}
    int x;
};

template<typename T>
class Printer {
public:
    const T& object;

    Printer(const T& object) : object(object) {}

    void print() {
        cout << object << endl;
    }
};

class Derived : public Base {
public:
    using Base::Base;
};

std::ostream& operator<<(std::ostream& out, const Derived& d) {
    return out << d.x;
}

int main() {
    Derived d(1);

    Printer<Derived>(d).print();
}

Now I'd like to avoid direct usage of Printer and allow such syntax: Derived d(1); d.print();. Thus I tried to inherit Derived also from Printer<Derived>.

class Derived : public Base, public Printer<Derived> {
public:
    typedef Printer<Derived> MyPrinter;

    using Base::Base;

    Derived() : MyPrinter(*this) {}
};  

Now I have a problem: Base constructors know nothing about Printer and thus cannot initialize it in any way. I also cannot use a constructor delegation here because the constructor which is used in Derived is actually inherited from Base.

Can I somehow make the default constructor of Derived be delegated by any other constructor, even inherited ones? Or maybe there is some other patterns to initialize the second base in multiple inheritance?

One more thing which hardens everything is that I don't have access to Base's code and only can use it as is.

UPDATE On Remy Lebeau's answer: Base can have multiple constructors which I'm not aware of (it is a template class as well), so I can't implement all of them and must use using Base::Base idiom.

On krzaq's answer: Printer actually also has many methods, not only print(), so implementing a forwarder class is a nuisance and I try to avoid it.

Upvotes: 0

Views: 71

Answers (4)

RiaD
RiaD

Reputation: 47650

You may create one variadic template catch-all constructor that will pass arguments to Base and construct Printer as needed, like this

class Derived: ... {
   template<typename... Args>
   Derived(Args&&... args): Base(std::forward<Args>(args)...), Printer(*this) {}
}

Upvotes: 1

Ivan Smirnov
Ivan Smirnov

Reputation: 4435

I've found an amazingly simple solution. Let's store in Printer a pointer to T instead of a reference.

template<typename T>
class Printer {
public:
    const T* objectPtr;

    Printer() : objectPtr(nullptr) {}
    Printer(const T& object) : objectPtr(&object) {}

    void print() {
        if (objectPtr) {
            cout << *objectPtr << endl;
        } else {
            cout << static_cast<const T&>(*this) << endl;
        }
    }
};

It looks not really safe but making Printer() ctor private seems to make the deal.

Upvotes: 0

krzaq
krzaq

Reputation: 16431

If all you need is to have access to Derived instance from Printer<Derived> then you can simply cast it down:

template<typename T>
class Printer {
public:
    const T& object;

    Printer() : object(static_cast<T&>(*this)) {}

    void print() {
        cout << object << endl;
    }
};

live demo

or make away with the reference altogether and make your class eligible for EBO:

template<typename T>
class Printer {
public:
    void print() {
        cout << static_cast<T&>(*this) << endl;
    }
};

live demo

If you can't/don't want to touch Printer either, I'd create a separate template PrinterForwarder to firward the print() call to the right printer:

template<typename T>
class PrinterForwarder
{
public:
    void print() {
        Printer<T>(static_cast<T&>(*this)).print();
    }
};

class Derived : public Base, public PrinterForwarder<Derived> {
public:
    using Base::Base;
};

live demo

Upvotes: 1

Remy Lebeau
Remy Lebeau

Reputation: 597875

Now I have a problem: Base constructors know nothing about Printer and thus cannot initialize it in any way.

To do what you are attemping, you won't be able to use a using Base::Base statement anymore. You will have to be more explicit about the constructors that Derived implements, so they can each initialize the Printer base class as needed, eg:

class Derived : public Base, public Printer<Derived> {
public:
    typedef Printer<Derived> MyPrinter;

    Derived() : Base(0), MyPrinter(*this) {}
    Derived(int x) : Base(x), MyPrinter(*this) {}
};  

Or:

class Derived : public Base, public Printer<Derived> {
public:
    typedef Printer<Derived> MyPrinter;

    Derived() : Derived(0) {}
    Derived(int x) : Base(x), MyPrinter(*this) {}
};  

Upvotes: 1

Related Questions