1
\$\begingroup\$

In an application that uses heavily polymorphic classes, I have often the need to downcast a pointer or a reference to the correct derived class. They should always be of the correct derived class, but for verification / unit testing, it would be great if this could be automatically found.
Therefore, I wrote a very short down_cast<targettype>(sourcetype) template.

The idea is that

  • the code uses down_cast<>(), to be used and looking like static_cast<>() or dynamic_cast<>()
  • I can switch on RTTI for the Debug build, and it will use dynamic_cast, plus verify if the object are of the expected types
  • I can switch off RTTI for the Release build, and it will use static_cast (without verification or overhead)

[This is my first venture into templates, so it might look elementary to you, sorry.]

The template:

#include <assert.h>
#include <type_traits>

template <typename T, typename S>
inline T down_cast(S&& s)
{
#ifdef _CPPRTTI
  T p = dynamic_cast<T>(s);
  if constexpr (std::is_pointer<T>::value)
    assert(p != nullptr);
  return p;
#else
  return static_cast<T>(s);
#endif
}

and here an example that uses it:

class A
{
  virtual void foo() {};
};

class B : public A
{};

class C : public A  // a C is NOT a B!
{};

int main()
{
  A* a = new B;

  B* b = down_cast<B*>(a);    // ok
  B& br = down_cast<B&>(*a);  // ok

  C* c = down_cast<C*>(a);    // not ok, fails with RTTI but 'works' without RTTI
  C& cr = down_cast<C&>(*a);  // not ok, fails with RTTI but 'works' without RTTI
}

It works as I want it to; the last two lines are bad, and will be found with this approach; the others run fine.

Questions: Does this make any sense; is it done right; what could I do better?

Edit: I am aware that _CPPRTTI is Visual Studio specific; also, I could simply make a macro to replace down_cast but that would not be able to test the pointer, and I don't want to use macros.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Also, I can't see how your two B lines are substantively different from your two C lines. They look exactly equivalent, to me. Did you mean, instead, to attempt a cast from a B to a C, rather than from an A to a C? \$\endgroup\$ Apr 21, 2022 at 10:35
  • \$\begingroup\$ @AdrianMole, the A* a is really a B*, not an A; check the first line in main where it is created. Downcasting this polymorphic A* to a B* is therefore valid, but downcasting it to a C* is not valid. \$\endgroup\$
    – Aganju
    Apr 21, 2022 at 18:59
  • \$\begingroup\$ @Agnanju Aha - OK. But now, when I test the code, it succeeds whichever part of the #if .. #else ... is used. (MSVC, VS 2022). \$\endgroup\$ Apr 21, 2022 at 19:34
  • 1
    \$\begingroup\$ Never mind - I'm just being dim. \$\endgroup\$ Apr 21, 2022 at 19:35

1 Answer 1

2
\$\begingroup\$

Add some class hierarchy compile-time guard:


template <typename T, typename S>
inline T down_cast(S&& s)
{
    using base_S = std::remove_pointer_t<std::remove_cvref_t<S>>;
    using base_T = std::remove_pointer_t<std::remove_cvref_t<T>>;
    static_assert(std::is_base_of_v<base_S, base_T>);
    // ...
}
\$\endgroup\$
2
  • \$\begingroup\$ Looks like this is a whole level better than what I built - it is compile-time, so it doesn't need RTTI at all, and works always. Thanks!! \$\endgroup\$
    – Aganju
    Apr 21, 2022 at 19:02
  • 1
    \$\begingroup\$ @Aganju No, it still needs RTTI, for your case (A* a = new B;) there is no way to guard bad_cast from B& to C& other than dynamic_cast \$\endgroup\$
    – frozenca
    Apr 21, 2022 at 23:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.