What is type erasure in C++?
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.
Is type erasure concept exist in C-language?
Are you a Java developer?
If yes you need to forget a LOT of things and learn another LOT.
Down casting means inheritance. So this is not C.
C can cast almost anything to anything (C assumes that you know what you are doing, contrary to Java, C# and others)
Type erasure is a Java concept because JRE doesn't have generics at run-time, only raw-types.
In C you have only binary code at run-time, just like in Assembly and that's all we need to have lightning-fast executables (remember that almost any compiler, framework or virtual machine is built in C or C++).
In C there is no such thing of RTTI (Run-Time Type Information) and we don't need this because the aim of C is program directly over the metal.
Template type erasure
Doing this with a compile-time check is, unfortunately, not feasible. You can, however, provide that functionality with a runtime check.
A map's value type can only be one single type, and Foo<T>
is a different type for each T
. However, we can work around this by giving every Foo<T>
a common base class, have a map of pointers to it, and use a virtual function to dispatch call()
to the appropriate subclass.
For this though, the type of the argument must also always be the same. As mentioned by @MSalters, std::any
can help with that.
Finally, we can wrap all that using the pimpl pattern so that it looks like there's just a single neat Foo
type:
#include <cassert>
#include <string>
#include <functional>
#include <any>
#include <unordered_map>
#include <memory>
struct Foo {
public:
template<typename T, typename FunT>
void set(FunT fun) {
pimpl_ = std::make_unique<FooImpl<T, FunT>>(std::move(fun));
}
// Using operator()() instead of call() makes this a functor, which
// is a little more flexible.
void operator()(const std::any& arg) {
assert(pimpl_);
pimpl_->call(arg);
}
private:
struct IFooImpl {
virtual ~IFooImpl() = default;
virtual void call( const std::any& arg ) const = 0;
};
template <class Arg, typename FunT>
struct FooImpl : IFooImpl
{
FooImpl(FunT fun) : fun_(std::move(fun)) {}
void call( const std::any& arg ) const override {
fun_(std::any_cast<Arg>(arg));
}
private:
FunT fun_;
};
std::unique_ptr<IFooImpl> pimpl_;
};
// Usage sample
#include <iostream>
void bar(int v) {
std::cout << "bar called with: " << v << "\n";
}
int main() {
std::unordered_map<std::string, Foo> table;
table["aaa"].set<int>(bar);
// Even works with templates/generic lambdas!
table["bbb"].set<float>([](auto x) {
std::cout << "bbb called with " << x << "\n";
});
table["aaa"](14);
table["bbb"](12.0f);
}
see on godbolt
Type erasure for methods with differing in return types
Type erasure can and has been implemented in C++ in different contexts. The most common approach, which is used in boost::any
, std::function< signature >
, std::thread
and others is based on a non-polymorphic class that is the type erased object, which contains a pointer to an interface type. Internally, during construction, assignment or whenever the user type is erased, an implementation of the interface is instantiated and stored.
As a motivating simplified example, consider that we wanted to create a printable
type that can be used to print any type that implements operator<<
to std::cout
using type erasure. For that we need the type printable
, the internal interface printable_impl_base
, and the actual implementations:
// regular polymorphic hierarchy:
struct printable_impl_base {
virtual ~printable_impl_base() {}
virtual void print() const = 0;
};
template <typename T>
struct printable_impl : printable_impl_base {
T copy_to_print;
printable_impl( T const & o ) : copy_to_print( o ) {}
virtual void print() const {
std::cout << copy_to_print << std::endl;
}
};
// type erasure is performed in printable:
class printable {
std::shared_ptr<printablable_impl_base> p;
public:
template <typename T>
printable( T obj ) : p( new printable_impl<T>(obj) ) {}
void print() const {
p->print();
}
};
Note that the pattern is very similar to a regular polymorphic hierarchy, with the difference that an interface object is added that is a value type (borrowing the term value type from C#), that holds the actual polymorphic objects inside.
Looking at it this way, it seems kind of simplistic and useless, but that is the fuel that drives boost::any
(the internal interface is only a typeid), std::function< void () >
(the internal interface is that it implements void operator()
), or shared_ptr<>
(the interface is the deleter method, that relinquishes the resource).
There is one specific different type of type erasure when the only thing that needs to be done with the type that implements type erasure is to destroy it: use a temporary and bind it to a constant reference... But this is very specific, if you want you can read about it here: http://drdobbs.com/cpp/184403758
In the specific case that you are talking about in the question it is a bit more complex, because you don't want to erase a single type, but rather a couple of them. The Iterable
interface must erase the type of the container that it internally holds, and in doing so it has to provide it's own iterators that have to perform type erasure on the iterators from the container. Still, the idea is basically the same, just more work to do to implement.
Type erasing type erasure, `any` questions?
Here's my solution. It looks shorter than Yakk's, and it does not use std::aligned_storage
and placement new. It additionally supports stateful and local functors (which implies that it might never be possible to write super_any<&print>
, since print
could be a local variable).
any_method:
template<class F, class Sig> struct any_method;
template<class F, class Ret, class... Args> struct any_method<F,Ret(Args...)> {
F f;
template<class T>
static Ret invoker(any_method& self, boost::any& data, Args... args) {
return self.f(boost::any_cast<T&>(data), std::forward<Args>(args)...);
}
using invoker_type = Ret (any_method&, boost::any&, Args...);
};
make_any_method:
template<class Sig, class F>
any_method<std::decay_t<F>,Sig> make_any_method(F&& f) {
return { std::forward<F>(f) };
}
super_any:
template<class...OperationsToTypeErase>
struct super_any {
boost::any data;
std::tuple<typename OperationsToTypeErase::invoker_type*...> operations = {};
template<class T, class ContainedType = std::decay_t<T>>
super_any(T&& t)
: data(std::forward<T>(t))
, operations((OperationsToTypeErase::template invoker<ContainedType>)...)
{}
template<class T, class ContainedType = std::decay_t<T>>
super_any& operator=(T&& t) {
data = std::forward<T>(t);
operations = { (OperationsToTypeErase::template invoker<ContainedType>)... };
return *this;
}
};
operator->*:
template<class...Ops, class F, class Sig,
// SFINAE filter that an op matches:
std::enable_if_t< std::disjunction< std::is_same<Ops, any_method<F,Sig>>... >{}, int> = 0
>
auto operator->*( super_any<Ops...>& a, any_method<F,Sig> f) {
auto fptr = std::get<typename any_method<F,Sig>::invoker_type*>(a.operations);
return [fptr,f, &a](auto&&... args) mutable {
return fptr(f, a.data, std::forward<decltype(args)>(args)...);
};
}
Usage:
#include <iostream>
auto print = make_any_method<void(std::ostream&)>(
[](auto&& self, auto&& os){ os << self; }
);
using printable_any = super_any<decltype(print)>;
printable_any bob = 7; // sets up the printing data attached to the any
int main() {
(bob->*print)(std::cout); // prints 7
bob = 3.14159;
(bob->*print)(std::cout); // prints 3.14159
}
Live
Type erasure: Retrieving value - type check at compile time
For a limited set of types, your best option is variant
. You can operate on a variant most easily by specifying what action you would take for every single variant, and then it can operate on a variant correctly. Something along these lines:
std::unordered_map<std::string, std::variant<Foo, Bar>> m;
m["a_foo"] = Foo{};
m["a_bar"] = Bar{};
for (auto& e : m) {
std::visit(overloaded([] (Foo&) { std::cerr << "a foo\n"; }
[] (Bar&) { std::cerr << "a bar\n"; },
e.second);
}
std::variant
is c++17 but is often available in the experimental namespace beforehand, you can also use the version from boost. See here for the definition of overloaded: http://en.cppreference.com/w/cpp/utility/variant/visit (just a small utility the standard library unfortunately doesn't provide).
Of course, if you are expecting that a certain key maps to a particular type, and want to throw an error if it doesn't, well, there is no way to handle that at compile time still. But this does let you write visitors that do the thing you want for each type in the variant, similar to a virtual in a sense but without needing to actually have a common interface or base class.
what is the logic of type erasure in java generics?
To complement @Giorgi Tsiklauri's answer,
For the class below:
class Box<T> {
T t;
T getT() { return t; }
void setT(T t) { this.t = t; }
}
because of type erasure, compiler compiles the class to:
class Box {
Object t;
Object getT() { return t; }
void setT(Object t) { this.t = t; }
}
So, when you instantiate the Box<T>
generic class with type argument Integer
as follows:
Box<Integer> box = new Box<Integer>();
box.setT(10);
Integer t = box.getT();
the compiler knows that the Box<T>
is instantiated with Integer
because, at compile time, it sees that Box<Integer>
. So, type casts are inserted by compiler. So the code above is compiled into:
Box box = new Box();
box.setT(10);
Integer t = (Integer) box.getT(); // compiler inserts type casts in lieu of us!
No matter how you instantiate the generic, the compiler doesn't create additional class files. If what actually happens at type erasure is replacement(replaces T
with type argument such as Integer
), the compiler have to create additional classes for Box
for Integer
, Box
for Double
etc — But that's not what actually happens at type erasure:
Replace all type parameters in generic types with their bounds or
Object
if the type parameters are unbounded. — The Java™ Tutorials
Insert type casts if necessary to preserve type safety. — The Java™ Tutorials
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead. — The Java™ Tutorials
Related Topics
How Do Memory_Order_Seq_Cst and Memory_Order_Acq_Rel Differ
Do I Have to Bind a Udp Socket in My Client Program to Receive Data? (I Always Get Wsaeinval)
Boost::Property_Tree Xml Pretty Printing
C++ #Include and #Import Difference
Why Can't We Declare a Namespace Within a Class
Error: Class Has Not Been Declared Despite Header Inclusion, and the Code Compiling Fine Elsewhere
What Is the Meaning of Template<> with Empty Angle Brackets in C++
Why Do We Have Reinterpret_Cast in C++ When Two Chained Static_Cast Can Do Its Job
Why Isn't There an Operator[] for a Std::List
Relative Paths Not Working in Xcode C++
Displaying Svg in Opengl Without Intermediate Raster
Generic Way to Print Out Variable Name in C++
What Does This C Code Do [Duff's Device]
Convert a Static Library to a Shared Library (Create Libsome.So from Libsome.A): Where's My Symbols
Object Oriented Programming in Haskell