为什么不能将此符合协议的类分配给协议类型的变量?

时间:2019-06-16 00:39:51

标签: swift swift-protocols

这里有一个玩具示例,找不到在线解决方案。

protocol Tree {
    associatedtype Element

    // some nice tree functions
}

class BinaryTree<T> : Tree {
    typealias Element = T
}

class RedBlackTree<T> : Tree {
    typealias Element = T
}


enum TreeType {
    case binaryTree
    case redBlackTree
}

// Use a generic `F` because Tree has an associated type and it can no
// longer be referenced directly as a type. This is probably the source
// of the confusion.
class TreeManager<E, F:Tree> where F.Element == E {
    let tree: F

    init(use type: TreeType){
        // Error, cannot assign value of type 'BinaryTree<E>' to type 'F'
        switch(type){
        case .binaryTree:
            tree = BinaryTree<E>()
        case .redBlackTree:
            tree = RedBlackTree<E>()
        }
    }
}

我不确定这是什么问题,还是应该寻找以便解决该问题的方法。我仍在考虑使用其他语言进行接口的协议,并且我将BinaryTree视为Tree的有效实现,并且对F的唯一限制是它必须成为Tree。为了使事情更加混乱,我不确定下面的代码片段为什么会编译,而上面的代码却没有。

func weird<F:Tree>(_ f: F){ }

func test(){
    // No error, BinaryTree can be assigned to an F:Tree
    weird(BinaryTree<String>())
}

任何指针或解释将不胜感激。

5 个答案:

答案 0 :(得分:0)

我不了解这种情况的背景。但是,我提供了两种解决方案:

1:

class Bar<F:Foo> {
    let foo: FooClass.F

    init(){
        foo = FooClass.F()
    }
}

2:

class Bar<F:Foo> {
    let foo: FooClass

    init(){
        foo = FooClass()
    }
}

对于正在尝试实现的目标,您当前正在做的没有逻辑意义

答案 1 :(得分:0)

我不知道您要做什么,但是当然不可能。从您的示例中,您尝试使用Bar创建generic类。但这不是创建generic对象的适当方法,因为generic对象的创建是使该对象接受任何类型。

以下是从Wikipedia摘录的generic的一些简要说明。

在第一段中,“ 通用编程是一种计算机编程样式,其中,算法根据 to-be-specified-later 类型进行编写,然后在作为参数提供的特定类型所需的。”

很清楚to-be-specified-later是什么意思,对吧:)

回到您的示例:

class Bar<F: Foo> {

    let foo: F

    init() {
        // Error, cannot assign value of type 'FooClass' to type 'F'
        foo = FooClass()
    }
}

根据上面的代码,类型参数F对类型Foo具有约束。并且,您尝试为foo变量创建一个具有具体实现的实例,即FooClass。这是不可能的,因为foo变量是F类型(抽象)。当然,我们可以像foo = FooClass() as! F这样向下转换它,但是foo仅限于FooClass,那么为什么还要烦恼泛型呢?

希望有帮助:)

答案 2 :(得分:0)

类似的事情似乎是合理的。关键是要尝试使用某种逻辑来确定TreeManager是何时使用Tree的一种还是另一种。

protocol TreePicker {
    associatedtype TreeType : Tree

    func createTree() -> TreeType
}

struct SomeUseCaseA<T>: TreePicker {
    typealias TreeType = RedBlackTree<T>

    func createTree() -> TreeType {
        return RedBlackTree<T>()
    }
}

struct SomeUseCaseB<T>: TreePicker {
    typealias TreeType = BinaryTree<T>

    func createTree() -> TreeType {
        return BinaryTree<T>()
    }
}

class TreeManager<T, Picker: TreePicker> where Picker.TreeType == T {
    let tree: T

    init(use picker: Picker){
        tree = picker.createTree()
    }

这引入了另一个关心选择树实现的协议,where子句指定选择器将返回类型为T的树。

我认为所有这些只是无法声明类型tree的{​​{1}}的结果。它有些冗长,但是基本上它必须具有通用接口的外观。我想我也问得很差。我应该发布尚未编译的版本,并要求解决方案,而不是更进一步,使所有人感到困惑。

答案 3 :(得分:0)

您的处理方法是错误的。在原始示例中,除了枚举值之外,还必须指定元素(E)和容器(BinaryTree或RedBlackTree)的类型。那没有道理。

相反,您应该构造管理器以将树作为构造函数参数,从而允许Swift推断通用参数,即

class TreeManager<E, F: Tree> where F.Element == E {
    var tree: F

    init(with tree: F) {
        self.tree = tree
    }
}

let manager = TreeManager(with: BinaryTree<String>())

或者,您应该根据最终目标是在Swift 5.1中使用不透明的返回类型(这里的示例显然不是现实情况)

答案 4 :(得分:-1)

protocol Foo {
    associatedtype F
}

class FooClass : Foo {
    typealias F = String
}

class Bar<M:Foo> {
    let foo: M

    init(){
        foo = FooClass() as! M
    }
}