Quaxton Hale
Quaxton Hale

Reputation: 2520

Alternative to dynamic casting

Is there an alternative to using dynamic_cast in C++?

For example, in the code below, I want to be able to have Cat objects purr. But only Cat objects and not Dog objects. I know this goes against deriving the class from Mammal since it's not very polymorphic, but I still want to know if I can do this without dynamic_cast.

My class declarations

class Mammal
{
       public: 
             virtual void Speak() const {cout << "Mammals yay!\n";}
};
class Cat: public Mammal
{
    public:
        void Speak() const{cout << "Meow\n";}
        void Purr() const {cout <"rrrrrrrr\n";}
};

class Dog: public Mammal
{
   public:
       void Speak() const{cout << "Woof!\n";}
};

In Main

int main()
{

    Mammal *pMammal;

    pMammal = new Cat;

    pMammal->Purr();     //How would I call this without having to use dynamic_cast?

    return 0;
}

Upvotes: 4

Views: 3236

Answers (4)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

If you know there is a fixed set of implementations, you can create virtual functions that do the casting for you. This can ne cheaper than dynamic_cast.

So:

struct Cat;
struct Mammal {
  virtual Cat* AsCat(){ return nullptr; }
};
struct Cat : Mammal {
  virtual Cat* AsCat() { return this; }
};

I have done this with template tomfoolery in C++11 so you can even make it look like a cast.

#include <utility>
#include <iostream>

template<typename T>
struct fast_castable_leaf {
  virtual T* do_fast_cast(T* unused=nullptr) { return nullptr; }
  virtual T const* do_fast_cast(T* unused=nullptr) const { return nullptr; }
  virtual ~fast_castable_leaf() {}
};
template<typename Tuple>
struct fast_castable;
template<template<typename...>class Tuple>
struct fast_castable<Tuple<>> {
  virtual ~fast_castable() {}
};
template<template<typename...>class Tuple, typename T, typename... Ts>
struct fast_castable<Tuple<T,Ts...>>:
  fast_castable_leaf<T>,
  fast_castable<Tuple<Ts...>>
{};
template<typename T> struct block_deduction { typedef T type; };
template<typename T> using NoDeduction = typename block_deduction<T>::type;
template<typename T>
T* fast_cast( NoDeduction<fast_castable_leaf<T>>* src ) {
  return src->do_fast_cast();
}
template<typename T>
T const* fast_cast( NoDeduction<fast_castable_leaf<T>> const* src ) {
  return src->do_fast_cast();
}

template<typename T, typename D>
struct fast_cast_allowed : std::integral_constant<bool,
  std::is_base_of<T,D>::value || std::is_same<T,D>::value
> {};

template<typename D, typename B, typename Tuple>
struct implement_fast_cast;

template<typename D, typename B, template<typename...>class Tuple>
struct implement_fast_cast<D,B,Tuple<>> : B {};
template<typename D, typename B, template<typename...>class Tuple, typename T, typename... Ts>
struct implement_fast_cast<D,B,Tuple<T,Ts...>> : implement_fast_cast<D, B, Tuple<Ts...>> {
private:
  D* do_cast_work(std::true_type) { return static_cast<D*>(this); }
  D const* do_cast_work(std::true_type) const { return static_cast<D const*>(this); }
  std::nullptr_t do_cast_work(std::false_type) { return nullptr; }
  std::nullptr_t do_cast_work(std::false_type) const { return nullptr; }
public:
  T* do_fast_cast( T* unused = nullptr ) override { return do_cast_work( fast_cast_allowed<T,D>() ); }
  T const* do_fast_cast( T* unused = nullptr ) const override { return do_cast_work( fast_cast_allowed<T,D>() ); }
};

And an example of the above framework in use:

struct Dog;
struct Cat;
struct Moose;
template<typename...>struct Types {};
typedef Types<Dog, Cat, Moose> Mammal_Types;

// A Mammal can be fast-casted to any of the Mammal_Types:
struct Mammal : fast_castable<Mammal_Types>
{};

// Cat wants to implement any legal fast_casts it can for Mammal in the
// set of Mammal_Types.  You can save on overhead by doing Types<Cat> instead
// of Mammal_Types, but this is less error prone:
struct Cat : implement_fast_cast< Cat, Mammal, Mammal_Types >
{};

int main() {
  Cat c;
  Mammal* m=&c;
  // so m is a pointer to a cat, but looks like a mammal.  We use
  // fast cast in order to turn it back into a Cat:
  Cat* c2 = fast_cast<Cat>(m);
  // and we test that it fails when we try to turn it into a Dog:
  Dog* d2 = fast_cast<Dog>(m);
  // This prints out a pointer value for c2, and 0 for d2:
  std::cout << c2 << "," << d2 << "\n";
}

Live Example

This can be cleaned up to support a more standard fast_cast<Cat*> instead of a fast_cast<Cat>, as well as fast_cast<Cat&>, then blocking direct access to do_fast_cast by making it private and fast_cast a friend, and allowing for some means to have virtual inheritance in the case that you need it.

But the core of the system is above. You get cast-to-derived at the cost of a single virtual function lookup without having to maintain much of the machinery yourself.


Alternative implementation:

template<class...>struct types{using type=types;};

template<typename T>
struct fast_castable_leaf {
  virtual T* do_fast_cast(T* unused=nullptr) { return nullptr; }
  virtual T const* do_fast_cast(T* unused=nullptr) const { return nullptr; }
  virtual ~fast_castable_leaf() {}
};
template<class Tuple>
struct fast_castable;
template<>
struct fast_castable<types<>> {
  virtual ~fast_castable() {}
};
template<class T0, class...Ts>
struct fast_castable<types<T0, Ts...>>:
  fast_castable_leaf<T0>,
  fast_castable<types<Ts...>>
{};
template<class T> struct block_deduction { typedef T type; };
template<class T> using NoDeduction = typename block_deduction<T>::type;
template<class T>
T* fast_cast( NoDeduction<fast_castable_leaf<T>>* src ) {
  return src->do_fast_cast();
}
template<class T>
T const* fast_cast( NoDeduction<fast_castable_leaf<T>> const* src ) {
  return src->do_fast_cast();
}

template<class T, class D>
struct fast_cast_allowed : std::integral_constant<bool,
  std::is_base_of<T,D>::value || std::is_same<T,D>::value
> {};

template<class Self, class Base, class Types>
struct implement_fast_cast;

template<class Self, class Base>
struct implement_fast_cast<Self,Base,types<>> : Base {
private:
  template<class, class, class>
  friend struct implement_fast_cast;

  Self* do_cast_work(std::true_type) { return static_cast<Self*>(this); }
  Self const* do_cast_work(std::true_type) const { return static_cast<Self const*>(this); }
  std::nullptr_t do_cast_work(std::false_type) { return nullptr; }
  std::nullptr_t do_cast_work(std::false_type) const { return nullptr; }
};

template<class Self, class Base, class T0, class... Ts>
struct implement_fast_cast<Self,Base,types<T0,Ts...>> :
  implement_fast_cast<Self, Base, types<Ts...>>
{
public:
  T0* do_fast_cast( T0* unused = nullptr ) override { return this->do_cast_work( fast_cast_allowed<T0,Self>() ); }
  T0 const* do_fast_cast( T0* unused = nullptr ) const override { return this->do_cast_work( fast_cast_allowed<T0,Self>() ); }
};

struct Dog;
struct Cat;
struct Moose;
typedef types<Dog, Cat, Moose> Mammal_Types;

struct Mammal : fast_castable<Mammal_Types>
{};

struct Cat : implement_fast_cast< Cat, Mammal, Mammal_Types >
{};

int main() {
  Cat c;
  Mammal* m=&c;
  Cat* c2 = fast_cast<Cat>(m);
  Dog* d2 = fast_cast<Dog>(m);
  std::cout << c2 << "," << d2 << "\n";
}

which might be easier for some compilers to swallow. Live example.

Note that for a long list of types, the above gets unwieldy (at both compile, and possibly run time), because it relies on linear inheritance.

A binary inheritance system would be a bit more complex to program, but would get rid of that problem. In it, you'd split your list of things to inherit from into two lists of equal size and inherit from both. The implement fast cast would have to inherit from Base via a virtual intermediary.

Upvotes: 6

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153830

C++ doesn't support sending messages as, e.g., Objective C or Smalltalk do. To call a method you need to have a statically typed handle for an object supporting the method. Whether you need to use a dynamic_cast<Cat*>(pointer) or if you can get away with something else, e.g., a static_cast<Cat*>(pointer) is a separate question.

Since dynamic_cast<...>() is relatively expensive and trying a potentially unbounded number of different classes isn't feasible, it may be preferable to use a visit() method in the base class which is called with a visitor. However, these are just techniques to get hold of a properly typed reference.

Upvotes: 3

TypeIA
TypeIA

Reputation: 17250

You're dealing with a pointer to type Mammal and presumably Mammal does not define Purr(). You absolutely must cast it to a pointer to type Cat in order to access Purr(). You can do this with a C-style cast or a dynamic_cast, and the latter is generally the more proper thing to do in C++. dynamic_cast also has the advantage that you can use it to test, at runtime, whether your Mammal object is a Cat or not, so you can decide whether you can call Purr().

Upvotes: 2

akhikhl
akhikhl

Reputation: 2578

Three variants:

  1. dynamic_cast, which you supposedly already aware of.

  2. static_cast, which is compile-time cast, i.e. a) types are checked for compatibility b) offset between base class and derived class is calculated and taken into account c) there's no runtime check.

  3. reinterpret_cast, which is also compile-time cast, done without any type check and without offset calculation. Use with caution - this cast might cause bugs very difficult to debug.

For complete reference and examples of these casts, look for some books and tutorials, for example: http://www.cplusplus.com/doc/tutorial/typecasting/

Upvotes: 0

Related Questions