Previous Entry Share Next Entry
Explaining Language Design (Part 6: Inheritance)
anime
he_the_great

This next post needs to take from essential complexity as this provides the explanation for the inheritence issue.

C++
template<typename T>
class Base {
public:
    void doBaseWork();
};

template<typename T>
class Derived: public Base<T> {
public:
    void doDerivedWork() {
        doBaseWork();
    }
};

C++ is doing eager error reporting. As we read this we can say, "yes Derived should be able to call doBaseWork()." However, we don't have enough information at this time.

C++
template<typename T>
class Base<int> { };

Now it is no longer allowed to call doBaseWork() if T is an int. This also applies to typos.

C++
template<typename T>
class Derived: public Base<T> {
public:
    void doDerivedWork() {
        dBseWrk();
    }
};

C++ will not allow this to compile because it can't validate that dBseWrk() exists while parsing the template. The language provides a means to get by this early lookup by using 'this->dBseWrk()' instead.

dlang
class Base(T) {
    public void doBaseWork() {}
}

class Derived(T) : Base!T {
    public void doDerivedWork() {
        doBaseWork();
    }
}

class Derived2(T) : Base!T {
    public void doDerivedWork() {
        dBseWrk();
    }
}
void main() {}

D has chosen to do later lookup. As Scott mentions, this means the error for calling dBseWrk() is pushed onto the library user instead of the library writer. There is no early lookup in D, it waits for instantiation when it has all the specializations and can choose/verify the appropriate calls. I also recommend using unittest blocks to get instances of your templates.

C++ doesn't solve the problem of offloading the error onto the user, it only makes the library writer be explicit when they are doing so. And even then, it is through reuse of a syntax which in no other context does it mean evaluate later.

Conclusion

D has chosen the side of design which makes the language easier to explain and understand. Quite simply, the above templates compile because they do not exist (no attempt was made to instantiate them).

D is also positioned to do a better job of identifying incorrect usage within a template, possibly through a static analyzer. In D the module is a compilation unit, it is not possible for an outside module to introduce a specialized template and hijack the behavior. By viewing a single module one knows all of the specializations and most likely do some type analysis to decide if call doBaseWork() will always be valid (don't even need to know the imported symbols).

  1. Part 1: Default Initialization
  2. Part 2: Const Inference
  3. Part 3: Lambdas in C++
  4. Part 4: Lambdas in D
  5. Part 5: Type Inference
  6. Part 6: Inheritance
  7. Part 7: Algorithm Complexity
  8. Part 8: Essential Complexity

?

Log in