Type Erasing Type Erasure, 'Any' Questions

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 Identifiables, 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



Leave a reply



Submit