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.
#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
