20

So I was reading this article about type erasure. But the code in that article seems partially incorrect, for example:

Repaired link

template <typename T>
class AnimalWrapper : public MyAnimal
{
    const T &m_animal;

public:
    AnimalWrapper(const T &animal)
        : m_animal(animal)
    { }

    const char *see() const { return m_animal.see(); }
    const char *say() const { return m_animal.say(); }
};

followed by

void pullTheString()
{
    MyAnimal *animals[] = 
    {
        new AnimalWrapper(Cow()), /* oO , isn't template argument missing? */
        ....
    };
}

These mistakes discouraged me from reading any further in the article.

Anyways; can anyone please teach what type erasure in C++ means, with simple examples?

I wanted to learn about it to understand how std::function works, but couldn't get my head around it.

FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
Angelus Mortis
  • 1,534
  • 11
  • 25

1 Answers1

24

Here's a very simple example of type erasure in action:

// Type erasure side of things

class TypeErasedHolder
{
  struct TypeKeeperBase
  {
    virtual ~TypeKeeperBase() {}
  };

  template <class ErasedType>
  struct TypeKeeper : TypeKeeperBase
  {
    ErasedType storedObject;

    TypeKeeper(ErasedType&& object) : storedObject(std::move(object)) {}
  };

  std::unique_ptr<TypeKeeperBase> held;

public:
  template <class ErasedType>
  TypeErasedHolder(ErasedType objectToStore) : held(new TypeKeeper<ErasedType>(std::move(objectToStore)))
  {}
};

// Client code side of things

struct A
{
  ~A() { std::cout << "Destroyed an A\n"; }
};

struct B
{
  ~B() { std::cout << "Destroyed a B\n"; }
};

int main()
{
  TypeErasedHolder holders[] = { A(), A(), B(), A() };
}

[Live example]

As you can see, TypeErasedHolder can store objects of an arbitrary type, and destruct them correctly. The important point is that it does not impose any restrictions on the types supported(1): they don't have to derive from a common base, for example.


(1) Except for being movable, of course.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Hm, why aren’t you using ``&&`` (universal reference(?)) for the constructor argument of ``TypeErasedHolder`` and ``std::forward`` to pass it on to the ``TypeKeeper``? (trying to understand here, it seems you’re always taking a copy and then moving from that copy instead of moving the original object) – Jonas Schäfer Jan 15 '16 at 16:52
  • 2
    @JonasWielicki To keep the "very simple" aspect of the example. Using a forwarding reference would also require using `std::remove_reference` for the template argument to `TypeKeeper`, etc. – Angew is no longer proud of SO Jan 15 '16 at 16:53
  • Thanks for the clarification! – Jonas Schäfer Jan 15 '16 at 16:57
  • @AngelusMortis There is no forwarding reference (formerly known as "universal referene") in the code now, just a plain old rvalue reference. But if you'd prefer, I can remove that too (or just get rid of moving altogether, and rely on plain old `const&` and copy). – Angew is no longer proud of SO Jan 15 '16 at 17:29
  • @Angew That's awesome, and thanks for treating noobies like me so nicely :) – Angelus Mortis Jan 15 '16 at 17:30
  • 5
    (2) except being destroyable, of course. As an aside, the movable requirement is easy to fix: add emplace based construction. `template struct emplace_as_t{}; template emplace_as_t emplace_as = {};` then add ctor `template TypeErasedHolder(emplace_as_t, Args&&...args)` etc. But that isn't basic type erasure. – Yakk - Adam Nevraumont Jan 15 '16 at 19:30