7
votes

From my wrapper class Pointer<Base> I'd like to only return pointers to const: Base const *.
When casting Pointer<Base> to Derived const * I get a compile error:

error C2440: 'static_cast': 'Pointer' can not be converted to 'const Derived *'

(translated from german VS2012)

struct Base { };

struct Derived : public Base { };

template <typename T>
class Pointer {
public:
    Pointer(T *t = nullptr) : p(t) { }

    //operator T*() { return p; }
    operator T const *() const { return p; }

    template <typename U>
    inline U staticCast() const { return static_cast<U>(d); }

private:
    T *p;
};

int main(int argc, char *argv[]) {
    Derived d;
    Pointer<Base> p(&d);

    Derived const *pd = static_cast<Derived const *>(p);
}

If I enable the conversion operator T*() { return p; } it works.

Why doesn't static_cast use the const conversion operator?

Or more specifically, since

Derived const *pd = static_cast<Derived const *>(static_cast<Base const *>(p));

works:

Why can static_cast implicitly cast to Base *, but not to Base const *, even though the latter is sufficient for the cast target type?


The standard says:

If there is an implicit conversion sequence from expression to new_type, or if overload resolution for a direct initialization of an object or reference of type new_type from expression would find at least one viable function, then static_cast(expression) returns the imaginary variable Temp initialized as if by new_type Temp(expression);, which may involve implicit conversions, a call to the constructor of new_type or a call to a user-defined conversion operator.

[Emphasis by me]


Workaround

Since this seems like a VisualStudio bug, I will use a workaround instead by means of a templated member function staticCast() (see sample code above), to be used like this:

Derived const *pd = p.staticCast<Derived const *>();

To allow only casts to U const *, use SFINAE:

template <typename U>
struct is_pointer_to_const
{
    static const bool value = std::is_pointer<U>::value
            && std::is_const<typename std::remove_pointer<U>::type >::value;
};

template <typename U>
inline U staticCast(typename std::enable_if<is_pointer_to_const<U>::value >::type* = 0) const
{ return static_cast<U>(d); }

template <typename U>
inline U staticCast(typename std::enable_if<!is_pointer_to_const<U>::value >::type* = 0) const
{ static_assert(false, "Type is not a pointer to const"); return U(); }
2

2 Answers

4
votes

There is only one conversion allowed, so you can convert to Base, but it cannot be converted afterwards to Derived.

So you have to use two consecutive casts. It's safer anyway because you state that you know that you are converting from a Base to a Derived. You should never have an implicit conversion from a base class to a derived class.

3
votes

You need to process in two steps as you're trying to convert Pointer<Base>* ---(1)---> Base const* ---(2)---> Derived const*, with:

  1. Pointer<Base>::operator Base const*
  2. downcast.

e.g.

Base const* pb = static_cast<Base const *>(p);
Derived const *pd = static_cast<Derived const*>(pb);

Live demo.