1

I can't access field overridden in child class from parent C++ constructor and I can not use templates, because upstream project doesn't use them.

This is working Python prototype that I try to reimplement in C++. The code contains two driver classes - one child and one parent, and parent prints name of the class during initialization.

class Driver(object):
  name = "Unknown"
  def __init__(self):
    print(self.name)

class SpecificDriver(Driver):
  name = "Specific"
  def __init__(self):
    super(SpecificDriver, self).__init__()

Driver()
SpecificDriver()

This prints two strings to console

Unknown
Specific

Looks like in C++ can't access overridden name, because the name doesn't exist at this point - Call child method from parent constructor. So maybe there is another way to get driver name printed on initialization?

UPDATE (2018): The original title for this question was "Print overridden child field during initialization in C++ without templates" and it was closed as too broad.

anatoly techtonik
  • 19,847
  • 9
  • 124
  • 140
  • You could use static polymorphism aka CRTP. – πάντα ῥεῖ May 01 '16 at 07:40
  • @πάνταῥεῖ does that requires using templates? In the codebase I am patching I don't see them, and it doesn't look simple at all. – anatoly techtonik May 01 '16 at 07:46
  • Yes that would require to make `Driver` a templated base class that takes the deriving class as type parameter. Thus you can use `static_cast(this)` every where you want to access the `SpecificDriver` members. Pretty simple IMHO. – πάντα ῥεῖ May 01 '16 at 07:49
  • 1
    I don't understand your reasoning why you can't use templates. What do you mean _"because upstream doesn't use them"_? – πάντα ῥεῖ May 01 '16 at 07:52
  • @πάνταῥεῖ I am trying to patch this project for debugging and the code doesn't use templates - https://github.com/godotengine/godot/pull/4352 – anatoly techtonik May 01 '16 at 08:30
  • @anatolytechtonik you already have the solution in the PR – tobspr May 01 '16 at 08:31
  • @anatolytechtonik I still can't see a reasoning from your link, what should hinder you to use templates. You're working on your own API right? Also the requirement that derived classes _must override `name`_ will work out of the box using CRTP. – πάντα ῥεῖ May 01 '16 at 08:35
  • @tobspr no solution in PR works - tests do not pass or the string printed is wrong. – anatoly techtonik May 01 '16 at 08:45
  • @πάνταῥεῖ if CRTP/templates is the only way to do this in constructor, then I can accept this as an answer that it is impossible to do this in C++ without templates. – anatoly techtonik May 01 '16 at 08:47

2 Answers2

2

Despite you're asking to do this without templates, it's the only way to do this from a base class constructor.

Here's a sample how it should be done:

struct IDriver {
    // Public virtual API:
    virtual void func1() = 0;
    // ...
    virtual ~IDriver() {}
};

template<typename Derived>
class Driver : public IDriver {
public:
    Driver() {
         std::cout << "Driver" << std::endl;
         std::cout << static_cast<Derived*>(this)->name() << std::endl;
    }   
};

class SpecificDriver : public Driver<SpecificDriver> {
public:
    // Public virtual API:
    virtual void func1();
    std::string name() const { return "SpecificDriver"; }
    // or use typeid(SpecificDriver).name() if you prefer
};

int main() {
    SpecificDriver sd;
}

Live Demo


As for your comment:

Is it possible to use additional init() function as in @tobspr method, but making name a field instead of function call?

Well, since the class name is a static property of these classes anyway, you can use a static const field like follows:

template<typename Derived>
class Driver : public IDriver {
public:
    Driver() {
         std::cout << name << std::endl;
         std::cout << Derived::name << std::endl;
    }   

private:
    static const std::string name;
};

template<typename Derived>
const std::string Driver<Derived>::name = "Driver";

class SpecificDriver : public Driver<SpecificDriver> {
public:
    static const std::string name;
};

const std::string SpecificDriver::name = "SpecificDriver";

int main() {
    SpecificDriver sd;
}

Live Demo

or even simplified use typeid():

#include <iostream>
#include <string>
#include <typeinfo>

template<typename Derived>
class Driver {
public:
    Driver() {
         std::cout << typeid(*this).name() << std::endl;
         std::cout << typeid(Derived).name() << std::endl;
    }  
};

class SpecificDriver : public Driver<SpecificDriver> {
};

int main() {
    SpecificDriver sd;
}

Live Demo

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
1

Assuming you have two classes Base and Derived, the Base constructor does not know anything about the Derived class, which makes it impossible to distinguish between both types.

Since you also can not (should not) call virtual methods in the constructor, a common pattern is to make an init method:

struct Base {
    virtual std::string get_name() { return "Base"; }
    void init() { std::cout << get_name(); }
};

struct Derived : public Base {
    virtual std::string get_name() { return "Derived"; }
};

// Later on ..
Base b;
b.init(); // Should print "Base"

Derived d;
d.init(); // Should print "Derived"

As you can see, this is definitely not the simplest solution. Using templates in this case would certainly be better.

tobspr
  • 8,200
  • 5
  • 33
  • 46
  • I tried to use new `init` method in class initialization and it doesn't work, probably because it doesn't created too - http://codepad.org/OTXOdVNC Maybe there is a way to access static properties of derived class? – anatoly techtonik May 01 '16 at 09:06
  • Why it is not possible to define "Base" and "Derived" as class properties and not methods? – anatoly techtonik May 01 '16 at 10:08