簡體   English   中英

Swift:同類型要求使泛型參數等效?

[英]Swift: Same-Type requirement makes generic parameters equivalent?

我正在使用 swift 5 並嘗試編譯以下代碼:

protocol BasicProtocol {
    associatedtype T
    var str: T {get set}
}

struct AItem<U>: BasicProtocol {
    typealias T = U
    var str: T
    
    init<G: StringProtocol>(str: G) where G == T {
        self.str = str
    }
}

我得到編譯錯誤:

error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
    init<G: StringProtocol>(str: G) where G == T {
                                            ^

如何使它們等效? 還是我不能?

謝謝。


更新1:

這是我遇到的問題:我想聲明結構“AItem”,希望它有一個泛型類型“T”。 而且這個泛型類型會有一些限制,比如:“T:StringProtocol”。 然后由於某種原因,我需要使用一個數組來加載這些結構體,並確保每個結構體的generics可以隨意設置。

我了解到有“類型擦除”可能可以解決這個問題。 所以我嘗試了這種方式,但似乎沒有成功。 出現了上述問題。


更新 2:

struct AItem<T: StringProtocol> {
    var aStr: T
}

var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]

看,如果你編譯這段代碼,你會得到一個編譯錯誤:

error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
            ^

如果我使用“var array: [AItem<String>]”,我將無法在數組中放置任何其他非“String”但實現了“StringProtocol”實例。

這就是為什么我說我要“確保每個結構的generics可以隨意設置”。


更新 3:

非常感謝@jweightman,現在我再次更新我的問題。

protocol ConstraintProtocol {}

extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......

struct AItem<T = which class has Implemented "ConstraintProtocol"> {
    var aPara: T
    init(aPara:T) {
        self.aPara = aPara
    }
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]

// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
    var result = handleItem(curItem)
    do something...
}


func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {

    if (item.T is ...) {
        do someThing
        return ......
    } else if (item.T is ...) {
        do someThing
        return ...
    }
    return nil
}

這是我的全部想法,但所有這些都是偽代碼。

似乎類型擦除是您問題的答案。 類型擦除模式的關鍵思想是將強類型但不兼容的數據(如AItem<String>AItem<Data> )放在另一個數據結構中,該數據結構以“不太精確”的類型(通常是Any )存儲它們。

類型擦除的一個主要缺點是你丟棄了類型信息——如果你以后需要恢復它以弄清楚你需要對數組中的每個元素做什么,你需要嘗試將你的數據轉換為每個元素可能的類型,可能是混亂和脆弱的。 出於這個原因,我通常會盡可能地避免它。

無論如何,這是基於您的偽代碼的類型擦除示例:

protocol ConstraintProtocol {}

extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}

struct AItem<T: ConstraintProtocol> {
    var aPara: T

    init(aPara: T) {
        self.aPara = aPara
    }
}

struct AnyAItem {
    // By construction, this is always some kind of AItem. The loss of type
    // safety here is one of the costs of the type erasure pattern.
    let wrapped: Any
    
    // Note: all the constructors always initialize `wrapped` to an `AItem`.
    // Since the member variable is constant, our program is "type correct"
    // even though type erasure isn't "type safe."
    init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
        self.wrapped = wrapped
    }
    
    init<T: ConstraintProtocol>(aPara: T) {
        self.wrapped = AItem(aPara: aPara);
    }
    
    // Think about why AnyAItem cannot expose any properties of `wrapped`...
}

var anArray: [AnyAItem] = [
    AnyAItem(aPara: "String"),
    AnyAItem(aPara: 1234),
    AnyAItem(aPara: "a path".data(using: .utf8)!)
]

for curItem in anArray {
    let result = handleItem(item: curItem)
    print("result = \(result)")
}

// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
    if (item.wrapped is AItem<String>) {
        return "String"
    } else if (item.wrapped is AItem<Data>) {
        return "Data"
    } else if (item.wrapped is AItem<Int>) {
        return "Int"
    }
    return "unknown"
}

您可以考慮使用類型擦除的替代方法,如果您的泛型可以采用一小部分有限的具體類型,這種方法效果很好,那就是使用具有關聯值的枚舉來定義“總和類型”。 如果您感興趣的協議來自您無法更改的庫,這可能不是一個好的選擇。 在實踐中,sum 類型可能如下所示:

enum AItem {
    case string(String)
    case data(Data)
    case int(Int)
}

var anArray: [AItem] = [
    .string("String"),
    .int(1234),
    .data("a path".data(using: .utf8)!)
]

for curItem in anArray {
    let result = handleItem(item: curItem)
    print("result = \(result)")
}

func handleItem(item: AItem) -> String {
    // Note that no casting is required, and we don't need an unknown case
    // because we know all types that might occur at compile time!
    switch item {
    case .string: return "String"
    case .data: return "Data"
    case .int: return "Int"
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM