New overloadable operator: dynamic_cast

370 views
Skip to first unread message

DeadMG

unread,
Feb 10, 2013, 6:16:06 AM2/10/13
to std-pr...@isocpp.org
I propose the ability for the user to overload the dynamic_cast operator. There are two main use cases in the language right now:

Firstly, smart pointers. They can mimic the interface of raw pointers almost exactly, but not in this regard. It also makes treating smart pointers uniformly impossible, as each vendor has to introduce their own pseudo-dynamic-cast.
Secondly, many existing systems, such as LLVM and ICU, currently implement their own dynamic_cast, essentially, for their own types and classes. Being able to overload dynamic_cast would enable a consistent interface for these customised systems, as they can simply delegate the implementation.

Finally, unlike other operators, this would effectively involve overloading the primitive operator on a primitive type (pointers) for some cases.

template<typename T> class shared_ptr {
    ...
    template<typename U> shared_ptr<typename std::remove_pointer<U>::type> operator dynamic_cast() {
        return std::dynamic_pointer_cast<typename std::remove_pointer<U>::type>(*this);
    }
};

Here, the shared_ptr's operator is called when attempting to cast it, such as

int main() {
    std::shared_ptr<X> x;
    auto y = dynamic_cast<Y*>(x);
}

Here, if anyone (probably just the implementation) wishes to have the primitive semantics of dynamic_cast, they can simply take the address of x. Also notice that unlike std::dynamic_pointer_cast, the template argument is of the same form as the argument to the primitive operator.

class Value {
    ...
    template<typename Other> Other operator dynamic_cast(llvmFunction* p) {
        return llvm::dyn_cast<typename std::remove_pointer<Other>::type>(this);
    }
};

When the argument to dynamic_cast is a pointer, then the custom dynamic_cast operator is invoked on pointers or references of this type, hiding the primitive operator. In this case, it is assumed that the custom functionality essentially replaces the primitive operator's implementation, rather than extends its' interface as in shared_ptr. I don't propose a means to retrieve the original operator's behaviour in this case.

int main() {
    Value* p = ...;
    auto constant = dynamic_cast<Constant*>(p); // Calls overloaded operator.
}

I think that, given the prevalence of both user-defined pointer-imitating types, and home-rolled RTTI replacements, it is worth a further extension of flexibility as to how the language operates on pointers.

Feedback plix.

Ville Voutilainen

unread,
Feb 10, 2013, 8:50:55 AM2/10/13
to std-pr...@isocpp.org
> template<typename Other> Other operator dynamic_cast(llvmFunction* p) {
> return llvm::dyn_cast<typename
> std::remove_pointer<Other>::type>(this);
> }
> };
> When the argument to dynamic_cast is a pointer, then the custom dynamic_cast
> operator is invoked on pointers or references of this type, hiding the
> primitive operator. In this case, it is assumed that the custom
> functionality essentially replaces the primitive operator's implementation,
> rather than extends its' interface as in shared_ptr. I don't propose a means
> to retrieve the original operator's behaviour in this case.
>
> int main() {
> Value* p = ...;
> auto constant = dynamic_cast<Constant*>(p); // Calls overloaded
> operator.
> }
> I think that, given the prevalence of both user-defined pointer-imitating
> types, and home-rolled RTTI replacements, it is worth a further extension of
> flexibility as to how the language operates on pointers.
> Feedback plix.

I don't see a reason why this would be done with an overloadable
dynamic_cast operator
rather than with a generic dynamic_pointer_cast function that the
libraries can specialize.
The lookup of an overloaded operator for T in
dynamic_cast<whatever>(T*) looks seriously
odd, but that would be easily doable for the library solution. Then
again, in order to
allow for partial specializations, perhaps we need a trait rather than
a function template.

DeadMG

unread,
Feb 10, 2013, 9:32:46 AM2/10/13
to std-pr...@isocpp.org
Why do we use an overloaded operator<< rather than a free function? Or any overloaded operator, for that matter.

Overloading an operator has the advantage that it can match existing solutions which simply call dynamic_cast, and we don't end up with both a language *and* library feature that perform the same function.

Ville Voutilainen

unread,
Feb 10, 2013, 9:35:45 AM2/10/13
to std-pr...@isocpp.org
On 10 February 2013 16:32, DeadMG <wolfei...@gmail.com> wrote:
> Why do we use an overloaded operator<< rather than a free function? Or any
> overloaded operator, for that matter.

For notational convenience. There is no such convenience difference
between dynamic_cast<T>
and dynamic_pointer_cast<T>.

> Overloading an operator has the advantage that it can match existing
> solutions which simply call dynamic_cast, and we don't end up with both a
> language *and* library feature that perform the same function.

Existing solutions don't exist - dynamic_cast doesn't currently work
for smart pointers
the way you want them to. And the language and the library feature
wouldn't perform
the same function - the library feature would be a superset of the
core functionality.
That seems just about right to me.

DeadMG

unread,
Feb 10, 2013, 12:12:39 PM2/10/13
to std-pr...@isocpp.org
Except for the part where the primitive dynamic_cast now has absolutely nothing going for it whatsoever. It's exactly the same as the library version, except much less generic and flexible, and has many legacy calls to it.

Existing solutions don't exist - dynamic_cast doesn't currently work for smart pointers the way you want them to.

That doesn't mean that there isn't existing code that isn't, say,

template<typename T> void f(T t) {
    if (auto p = dynamic_cast<X*>(t)) {
        //...
    }
    //...
}

Even a raw pointer specific version taking T* would still be upgraded for use-cases with LLVM-style RTTI replacements without change, unlike your library-based proposal. 

Nicol Bolas

unread,
Feb 10, 2013, 2:34:42 PM2/10/13
to std-pr...@isocpp.org

And that last part is exactly why we shouldn't have this.

When I see `dynamic_cast` in code, I know that it means "use the language feature". Therefore, I know that it will always work across all C++ implementations the same way. I should not expect `dynamic_cast` to invoke some user-created casting system.

The fact that we need hacks like `std::addressof` to be sure we're getting the address of an object in template code is enough of an eye-sore. What kind of hack would you suggest for users who want to use the actual language feature, rather than some user-defined code that requires macros or some other nonsense?

Jeffrey Yasskin

unread,
Feb 10, 2013, 2:41:52 PM2/10/13
to std-pr...@isocpp.org
I was thinking that we can already overload static_cast<T>(u) by
defining U::operator T(), and so it makes sense to be able to define
dynamic_cast in a parallel way. But then I remembered that C++11 also
defines static_pointer_cast and const_pointer_cast, so overloading
dynamic_cast to accomplish what dynamic_pointer_cast currently
accomplishes, would not in fact be parallel with static_cast. So +1 to
using dynamic_pointer_cast<> for this.
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> Visit this group at
> http://groups.google.com/a/isocpp.org/group/std-proposals/?hl=en.
>
>

DeadMG

unread,
Feb 10, 2013, 2:57:49 PM2/10/13
to std-pr...@isocpp.org
It doesn't require macros or any other nonsense. The interface for dynamic_cast is exactly the same.

The fact that we need hacks like `std::addressof` to be sure we're getting the address of an object in template code is enough of an eye-sore.

There's a very big difference- primarily that for all existing types where dynamic_cast is legal, a dynamic_cast overload should act exactly the same. It's only the implementation that people are looking to replace. The whole point is to keep a *uniform* interface for all existing types, even those with homegrown implementations, and extend the range of types where it is legal. On the other hand, the entire purpose of overloading operator& is to give a *different* interface. 

There's no uniform interface in having to use std::dynamic_pointer_cast for one pointer type, dynamic_cast for some others, and llvm::dyn_cast for yet others.

 But then I remembered that C++11 also defines static_pointer_cast and const_pointer_cast

The issue is that for primitive types, explicit conversions and explicit casts aren't really the same thing. Strictly, for an existing pointer, you can't do

Base* b = nullptr;
Derived* d(b);

but you can do

Base* b = nullptr;
Derived* d = static_cast<Derived*>(d);

So arguably, if you want all the casts to be treated equivalently, you would have to add overloading for static_cast and const_cast. Adding U::operator T() does not produce the same semantics, as you can't enable static_casting but disable explicit conversion in other contexts. This is another place where primitives have features that UDTs can never have, which is wrong.

Richard Smith

unread,
Feb 10, 2013, 5:12:45 PM2/10/13
to std-pr...@isocpp.org
On Sun, Feb 10, 2013 at 3:16 AM, DeadMG <wolfei...@gmail.com> wrote:
Speaking as an LLVM developer, I don't think we would use this, even
if it became available in every compiler we care about. Using a
different name for our own dynamic cast operation is an advantage, not
a problem to be solved -- for instance, we can currently trivially
tell if a dynamic cast operation will be lightweight by how it is
spelled.

Nicol Bolas

unread,
Feb 10, 2013, 6:07:51 PM2/10/13
to std-pr...@isocpp.org

I've never used LLVM, so I'm wondering what makes dyn_cast more efficient than dynamic_cast? Ignoring the issue of executable bloat, that is. Or is it more runtime efficient than dynamic_cast at all? That is, if you implemented dyn_cast in some way that's more efficient than dynamic_cast, why can't the C++ compiler implement it the same way?

Chandler Carruth

unread,
Feb 10, 2013, 6:51:10 PM2/10/13
to std-pr...@isocpp.org
On Sun, Feb 10, 2013 at 3:07 PM, Nicol Bolas <jmck...@gmail.com> wrote:
I've never used LLVM, so I'm wondering what makes dyn_cast more efficient than dynamic_cast? Ignoring the issue of executable bloat, that is. Or is it more runtime efficient than dynamic_cast at all? That is, if you implemented dyn_cast in some way that's more efficient than dynamic_cast, why can't the C++ compiler implement it the same way?

We assume we know the total set of types involved in the type hierarchy and encode them in an enum in the base class. This lets the implementation be extremely efficient, often a single comparison against a constant. It works for LLVM because we know the bounds of the type hierarchy, but it doesn't work for the generic C++ case because it can't know that bound.

The problem with implementing this in a normal C++ dynamic_cast is that it is hard to express the concept "class A can only ever be subclassed by B, C, and D". Marking any particular leaf class as final doesn't really help that much.
Reply all
Reply to author
Forward
0 new messages