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
How can I erase generics from a type in Swift?
For question Is there a better way to declare the cups array?:
You can use another protocol called DrinkGeneric like this and implement it by Cup Struct:
protocol DrinkGeneric {
func sample()
func typOfDrink() -> Drink.Type
}
struct Cup<T: Drink>: DrinkGeneric {
public var drink: T?
mutating func bottomUp() {
drink = nil
}
public func typeOfDrink() -> Drink.Type {
return type(of: drink!)
}
func sample() {
print("sample")
}
}
Then create an array with type DrinkGeneric like this:
var cups: [DrinkGeneric] = [Cup(drink: Water()), Cup(drink: Tea())]
For check type:
if cups[0].typeOfDrink() is Water.Type {
// Any work
}
When to use type erasure in Swift?
I've tried to find a minimalistic example of type erasure. From my experience, it often gets more complex, and I try to avoid it as much as possible. But sometimes that's the way.
It is finally the same complexity as before, with old-school language. Excepted that old-school language was hurting you by crashing, while swift hurts you at build time.
It is meant to be a strongly typed language, so it does not fit well with generics.
Suppose you need to manage some shapes in a document.
These shapes are Identifiable
s, meaning they have an id which type is determined by an associated type. Int in this case.
The code below won't build because it can't use the Shape
protocol directly since the type of id
is an associated type that is defined by object conforming to the Shape protocol
import Foundation
protocol Shape: AnyShape, Identifiable {
var name: String { get }
}
struct Square: Shape {
var id: Int = 0
var name: String { "Square" }
}
func selectShape(_ shape: Shape) {
print("\(shape.name) selected")
}
By adding a type erased shape, you can then pass it to functions.
Thus, this will build:
import Foundation
protocol AnyShape {
var name: String { get }
}
protocol Shape: AnyShape, Identifiable {
}
struct Square: Shape {
var id: Int = 0
var name: String { "Square" }
}
func selectShape(_ shape: AnyShape) {
print("\(shape.name) selected")
}
Simple use case.
Suppose now our app connects to two shapes manufacturers servers to fetch their catalog and sync with our's.
We know shapes are shapes, all around the world, but the ACME Shape Factory index in database is an Int
, while the Shapers Club use UUID
..
That's at this point we need to 'recover' the type, as you say.
It is exactly what's explained when looking in the AnyHashable
source doc.
Cast can't be avoided, and it is finally a good thing for the security of the app and the solidity of the models.
The first part is the protocols, and it may be verbose and become complex as the number of situations grows, but it will be in the communication foundation framework of the app, and should not change very often.
import Foundation
// MARK: - Protocols
protocol AnyShape {
var baseID: Any { get }
var name: String { get }
}
// Common functions to all shapes
extension AnyShape {
func sameObject(as shape: AnyShape) -> Bool {
switch shape.baseID.self {
case is Int:
guard let l = baseID as? UUID , let r = shape.baseID as? UUID else { return false }
return l == r
case is UUID:
guard let l = baseID as? UUID , let r = shape.baseID as? UUID else { return false }
return l == r
default:
return false
}
}
func sameShape(as shape: AnyShape) -> Bool {
return name == shape.name
}
func selectShape(_ shape: AnyShape) {
print("\(shape.name) selected")
}
}
protocol Shape: AnyShape, Identifiable {
}
extension Shape {
var baseID: Any { id }
}
The second part is the models - this will hopefully evolve as we work with more shape manufacturers.
The sensitive operation that can be done on shapes are not in this code. So no problem to create and tweak models and apis.
// MARK: - Models
struct ACME_ShapeFactory_Model {
struct Square: Shape {
var id: Int = 0
var name: String { "Square" }
var ACME_Special_Feature: Bool
}
}
struct ShapersClub_Model {
struct Square: Shape {
var id: UUID = UUID()
var name: String { "Square" }
var promoCode: String
}
}
Test
let shape1: AnyShape = ACME_ShapeFactory_Model.Square()
let shape2: AnyShape = ShapersClub_Model.Square()
let shape3: AnyShape = ShapersClub_Model.Square()
Compare two different shapes references from different manufacturers
shape1.sameObject(as: shape2) : false
-> Logic, it can't be the same item if it comes from different manufacturers
Compare two different shapes references from same manufacturers
shape2.sameObject(as: shape3) : false
-> This is a new shape, sync in catalog
Compare two identical shapes references from same manufacturers
shape2.sameObject(as: shape2) : true
-> We already have this one in the catalog
Compare two shapes from different manufacturers
shape1.sameShape(as: shape2) : true
-> Dear customer, we have two kind of squares from two manufacturers
That's all. I hope this may be of any help.
Any correction or remark is welcome.
Last word, I am quite proud of the name of my Shapes manufactures :)
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
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.
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
Related Topics
Preparation for Std::Iterator Being Deprecated
Why Are Memcpy() and Memmove() Faster Than Pointer Increments
Is Accessing Data in the Heap Faster Than from the Stack
How to Declare a Global Variable in C++
Accessing Certain Pixel Rgb Value in Opencv
Installing C++ Libraries on Os X
What Does '&' Do in a C++ Declaration
Stack-Buffer Based Stl Allocator
How to Compile/Link Boost with Clang++/Libc++
What Does the Symbol \0 Mean in a String-Literal
C++: How to Round a Double to an Int