C++ Logo

std-proposals

Advanced search

[std-proposals] Language support for type erasure

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Fri, 10 Apr 2026 10:39:31 +0100
In a job interview recently I was given the following code:

    #include <iostream>
    #include <memory>
    #include <string>

    struct Animal {
      virtual ~Animal() = default;
      virtual std::string name() const = 0;
    };

    struct Cat : Animal {
      std::string name(void) const override
      {
        return "Cat";
      }
    };

    struct Dog : Animal {
      std::string name(void) const override
      {
        return "Dog";
      }
    };

    /* DO NOT CHANGE THE CODE BELOW! */

    void print_name(Animal const &a)
    {
      std::cout << a.name() << std::endl;
    }

    int main(void)
    {
      Cat cat;
      Dog dog;
      print_name(cat);
      print_name(dog);
    }

I was told to remove the inheritance, but I wasn't allowed to edit
code beneath the indicated line. So here's what I gave them back:

    struct Cat {
      string name(void) const
      {
        return "Cat";
      }
    };

    struct Dog {
      string name(void) const
      {
        return "Dog";
      }
    };

    struct Animal {
      std::function< string(void) > name;

      template<typename T>
      Animal(T &&arg)
      {
        name = [&arg](void){ return arg.name(); };
      }
    };

Now let's consider if there were multiple member functions:

    struct Cat {
      string name(void) const
      {
        return "Cat";
      }
      string weight(void) const
      {
        return "4kg";
      }
      string height(void) const
      {
        return "20cm";
      }
    };

    struct Dog {
      string name(void) const
      {
        return "Dog";
      }
      string weight(void) const
      {
        return "12kg";
      }
      string height(void) const
      {
        return "50cm";
      }
    };

    struct Animal {
      std::function< string(void) > name;
      std::function< string(void) > weight;
      std::function< string(void) > height;

      template<typename T>
      Animal(T &&arg)
      {
        name = [&arg](void){ return arg.name (); };
        weight = [&arg](void){ return arg.weight(); };
        height = [&arg](void){ return arg.height(); };
      }
    };

Instead of having multiple functor objects, we could do something like:

    struct Animal {
      std::function< string(unsigned) > f;

      template<typename T>
      Animal(T &&arg)
      {
        f =
          [&arg](unsigned const n) -> string
          {
            switch ( n )
            {
            case 0: return arg.name ();
            case 1: return arg.weight();
            case 2: return arg.height();
            }
          };
      }

      string name (void) const { return f(0); }
      string weight(void) const { return f(1); }
      string height(void) const { return f(2); }
    };

This got me thinking . . . . Should we have native language support for this?
For example let's say we were to define a class with the interface that we want:

    struct Interface {
      string name(void) const;
      string weight(void) const;
      string height(void) const;
    };

And then perhaps we would have a new kind of 'typedef' that creates
the type-erased class, something like:

    typedef<Interface> TypeErasedType;

And so then the entire program would look something like this:

    #include <iostream>
    #include <functional>
    #include <string>
    #include <utility>
    using std::string;

    struct Cat {
      string name(void) const
      {
        return "Cat";
      }
      string weight(void) const
      {
        return "4kg";
      }
      string height(void) const
      {
        return "20cm";
      }
    };

    struct Dog {
      string name(void) const
      {
        return "Dog";
      }
      string weight(void) const
      {
        return "12kg";
      }
      string height(void) const
      {
        return "50cm";
      }
    };

    struct Interface {
      string name(void) const;
      string weight(void) const;
      string height(void) const;
    };

    typedef<Interface> Animal;

    /* DO NOT CHANGE THE CODE BELOW! */

    void print_name(Animal const &a)
    {
      std::cout << a.name () << std::endl;
      std::cout << a.weight() << std::endl;
      std::cout << a.height() << std::endl;
    }

    int main()
    {
        Cat cat;
        Dog dog;
        print_name(cat);
        print_name(dog);
    }

Another nice thing about this is that we would get a very intuitive
compiler warning if the supplied type doesn't fulfil all the criteria
of the type-erased type -- it can clearly tell us that the supplied
type is missing a member function that's required for the interface.

Received on 2026-04-10 09:39:48