Strange template class conversion through the base class pointer

Viewed 57

Why this code is valid in C++:

class Base
{
public:
    Base() = default;

    // base class stuff...
};

template<typename NumericType>
class Numeric : public Base
{
public:
    Numeric() : m_value() {}

    void setValue(NumericType value) { m_value = value; }
    NumericType value() const { return m_value; }

private:
    NumericType m_value;
};

int main()
{
    auto integerPtr = new Numeric<int>();
    Base *basePtr = integerPtr;

    if (auto doublePtr = static_cast<Numeric<double>*>(basePtr)) {
        doublePtr->setValue(6543423.634234);
        std::cout << "Wow: " << doublePtr->value() << std::endl;
    }

    return 0;
}

To be honest, I was expecting compilation errors or at least that static_cast will fail but, this code example compiles, runs, and even works correctly.

Compiled with MCVS2019 32bit.

1 Answers

See static_cast from cppreference:

  1. If new_type is a reference or pointer to some class D and expression is lvalue of its non-virtual base B or prvalue pointer to it, static_cast performs a downcast. (This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D.) Such a downcast makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means, such as when implementing static polymorphism. Safe downcast may be done with dynamic_cast.

Your code invokes undefined behavior, because the cast is ill-formed. You should use dynamic_cast for up/downcasts. Provide a virtual destructor in Base then this code:

#include <iostream>

class Base
{
public:
    Base() = default;
    virtual ~Base() = default;
};

template<typename NumericType>
class Numeric : public Base
{
public:
    Numeric() : m_value() {}

    void setValue(NumericType value) { m_value = value; }
    NumericType value() const { return m_value; }

private:
    NumericType m_value;
};

int main()
{
    auto integerPtr = new Numeric<int>();
    Base *basePtr = integerPtr;

    if (auto doublePtr = dynamic_cast<Numeric<double>*>(basePtr)) {
        doublePtr->setValue(6543423.634234);
        std::cout << "Wow: " << doublePtr->value() << std::endl;
    }

    return 0;
}

Is correct and produces no output as expected.

Why this code is valid in C++

It is not valid code. Remember that incorrect code does not necessarily produce compiler errors. Anything can happen when your code has undefined behavior.

PS: I would have expected your code to blow up when optimizations are turned on. To my surprise, it still appears to work (https://godbolt.org/z/czfobbfqo). Thats the nasty side of undefined behavior. It may go unnoticed and will only blow up once you shipped the code and make a demo in front of your customers ;).

Related