Quarkstrudel im Kopf

August 7, 2014 / Martin

Type Erasure in C++

Say you would like to implement some kind of command pattern in C++. All the objects that can be called in some way share a certain function. In the case of an event this function should be called to trigger some action. One way to implement this in C++ is to define some kind of interface/abstact command class offering a common invoke function. Every class then needs to implement this interface. To add a command object to a list you can now simply use this common type to handle the interaction with these objects.

There might be reasons why you don’t want to proceed in this way. Maybe you do not have access to the definition of the command class in question or don’t want to make use of inheritance. Why shouldn’t the compiler be able to solve this issue at compile time? Probably you think: this should work with templates. The reason why this might not work as expected is that a templated version of some kind always will have to carry around this type information. We somehow need to be able to forget type information sometimes and only keep a restricted type information.

An example where this paradigm is used is std::shared_ptr<T> where the stored value might have a pretty complicated destruction function that is implementation specific. Maybe a lock or some other resource must be released. If we were to pass a destruction object to the constructor of std::shared_ptr<T> it somehow has to restrict it to a simple call forgeting everything else of the type. Otherwise this destruction obect’s type would become part of the pointer type. A counter-example to this is std::vector: changing the allocator makes a change of the vector type necessary everywhere in the code.

Forgetting part of a type is called type erasure and I’ve included a little example to show one way how could be implementing for list with different element types that share a common function.

  #include <iostream>
  #include <vector>
  #include <memory>

  class List {
    private:
      // all elements of the list will share this interface
      class ElementConcept {
        public:
          virtual
          void print() const = 0;

          virtual ~ElementConcept() {
          }
      };

      // this is the where the magic happens: for every element type there
      // exists an inherited class from the abstract concept class
      template <typename T>
      class Element : public ElementConcept {
        public:
          typedef T value_type;

          template <typename T_>
          Element(T_&& element)
          : value_(std::forward<T_>(element)) {
          }

          virtual
          void print() const {
            value_.print();
          }

        private:
          value_type value_;
      };

    public:
      // perferct forwarding for the stored value above
      template <typename T>
      void add(T&& element) {
        list_.push_back(std::make_shared<Element<T>>(
              std::forward<T>(element)
              ));
      }

      void print() const {
        for (const std::shared_ptr<ElementConcept>& e : list_) {
          e->print();
        }
      }


    private:
      std::vector<std::shared_ptr<ElementConcept>> list_;
  };

The trick lies in the on-the-fly inheritance inside the List class. Every time you add an object to the list the compiler automatically implements a subclass for that type that forgets about all the type information but the function of interest. I guess you could also do nicer concept check through this.

For example we can now implement two unrelated classes A and B, instantiate them, add them to the list. A print call on the list results in the desired outcome.

  struct A {
    void print() const {
      std::cout << "woof" << std::endl;
    }
  };

  struct B {
    void print() const {
      std::cout << "quack" << std::endl;
    }
  };


  int main(int argc, const char* argv[])
  {
    A a;
    B b;

    List list;

    list.add(a);
    list.add(b);

    list.print();

    return 0;
  }