Swift is a moving target, all code tested using Xcode 9.2 & Swift 4.0.
First if you wish to insert A<T>
items into a set then the type must implement the Hashable
protocol. You can implement this protocol as needed but for illustrative purposes a simple approach is to require the type parameter T
to be Hashable
and to implement the protocol by indirecting to val
:
struct A<T> : Hashable where T : Hashable
{
var val : T
var hashValue: Int { return val.hashValue }
static func ==(lhs: A<T>, rhs: A<T>) -> Bool
{
return lhs.val == rhs.val
}
}
The rest of the answer does not require the above implementation, just that A<T>
is Hashable
.
Constraining your Set
to only containing instances of A<T>
for any and all T
is more of a challenge. One approach to this problem is to use an existential type, which in this context essentially allows the wrapping of differently typed value inside another non-generic wrapper. Swift has some pre-defined existential types, in particular AnyHashable
which you could use to define B
's var
:
struct B
{
var setOfA : Set<AnyHashable> = Set()
...
}
Unfortunately this does not constrain the set to only hold values of type A<T>
for any T
.
The type of setOfA
cannot be Set<A<AnyHashable>>
as that would require a value of type A<T>
for some T
to be cast-able to A<AnyHashable>
and Swift does not support that (researching variance might help understand why).
You might wonder why the type cannot be Set<Hashable>
. Unfortunately (in this context!) Hashable
is a protocol with a Self
requirement and Swift does not support using it as a generic type parameter in this context. AnyHashable
is a non-generic struct
which type erases (the existential type bit) the value it wraps.
So back to solving the problem of constraining the set to only contains A
's. One solution is to hide (private
) the set inside B
and provide a generic insertion function which only accepts A<T>
values, for any T
. The set value itself can then be made public using a read-only computed property. For example:
struct B
{
// private property so only B's functions can modify the set
private var _setOfA : Set<AnyHashable> = Set()
// public read-only property so _setOfA cannot be changed directly
var setOfA : Set<AnyHashable> { return _setOfA }
// public insert function which only accepts A<T> values, for any T
mutating func insert<T>(_ element : A<T>) -> (Bool, A<T>)
{
return _setOfA.insert(element)
}
}
Here is dome sample code using the above:
let x = A(val: 4) // x : A<Int>
let y = A(val: "hello") // y : A<String>
var z = B()
print(z.insert(x)) // insert being generic accepts any A<T>
print(z.insert(y))
print(z.insert(x)) // should return (false, x) as element already added
print("\(z)")
for member in z.setOfA
{
print("member = \(member) : \(type(of:member))")
if let intMember = member as? A<Int>
{
print(" intMember = \(intMember) : \(type(of:intMember))")
}
else if let stringMember = member as? A<String>
{
print(" stringMember = \(stringMember) : \(type(of:stringMember))")
}
}
This outputs:
(true, __lldb_expr_501.A<Swift.Int>(val: 4))
(true, __lldb_expr_501.A<Swift.String>(val: "hello"))
(false, __lldb_expr_501.A<Swift.Int>(val: 4))
B(_setOfA: Set([AnyHashable(__lldb_expr_501.A<Swift.String>(val: "hello")), AnyHashable(__lldb_expr_501.A<Swift.Int>(val: 4))]))
member = A<String>(val: "hello") : AnyHashable
stringMember = A<String>(val: "hello") : A<String>
member = A<Int>(val: 4) : AnyHashable
intMember = A<Int>(val: 4) : A<Int>
HTH