getsoubl
getsoubl

Reputation: 1055

CRTP and downcasting

Following up a similar question in previous post about downcasting and type safety , I wonder if the following example creates undefined behavior.

An instance of Base class has been created. No dynamic binding take place.
However, in the Base::interface function the instance of the Base class is casted to an instance of the Derived class . Is this safe? If yes , why ? Please find the piece of code below.

#include <iostream>
template <typename Derived>
struct Base{
  void interface(){
    static_cast<Derived*>(this)->implementation();
  }
};

struct Derived1: Base<Derived1>{
  void implementation(){
    std::cout << "Implementation Derived1" << std::endl;
  }
};
        
int main(){
  
  std::cout << std::endl;
  Base<Derived1> d1;
  d1.interface();
  std::cout << std::endl;
}

Upvotes: 1

Views: 231

Answers (1)

Quentin
Quentin

Reputation: 63144

There is no Derived that this new Derived * could point to, so it is definitely not safe: the cast itself has undefined behaviour, as described in [expr.static.cast]§11 (emphasis mine):

A prvalue of type “pointer to cv1 B”, where B is a class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a complete class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined.

You can mitigate this risk by restricting access to Base's constructor:

template <typename Derived>
struct Base{
    // Same as before...

protected:
    Base() = default;
};

This is better, but still allows for the same issue if someone were to accidentally define struct Derived2 : Base<AnotherDerived> { };. Preventing that can be done with a specific friend declaration, but has the downside of giving full access to Base's private members:

template <typename Derived>
struct Base{
    // Same as before...

private:
    friend Derived;
    Base() = default;
};

Note that this still lets Derived construct naked Base<Derived> objects in its member functions, but that's where I generally stop whacking moles.

Upvotes: 5

Related Questions