奇怪的泛型错误

Ixx*_*Ixx 28 generics swift

我正在尝试使用泛型实现以下结构.得到一个奇怪的编译器错误,无法弄清楚原因.

class Translator<T:Hashable> {...}

class FooTranslator<String>:Translator<String> {...}
Run Code Online (Sandbox Code Playgroud)

这个想法是Translator使用T作为字典中键的类型.这可以是例如String或枚举.子类提供具体的字典.

但它失败的原因是:"类型'字符串'不符合协议'Hashable'"

但String符合Hashable!我疯了吗?它也不适用于Int,它也符合Hashable.如果我将Hashable替换为Equatable也不起作用,这也应该由两者实现.

If I remove the type constraint, just for testing (where I also have to disable the dictionary, as I can't use anything not hashable as key there) - it compiles

class Translator<T> {...}

class FooTranslator<String>:Translator<String> {...}
Run Code Online (Sandbox Code Playgroud)

What am I doing wrong?

nhg*_*rif 102

首先,让我们解释一下错误:

鉴于:

class Foo<T:Hashable> { }

class SubFoo<String> : Foo<String> { }
Run Code Online (Sandbox Code Playgroud)

这里令人困惑的部分是我们希望"String"表示Swift定义的结构,它包含一组字符.但事实并非如此.

这里,"String"是我们给出新子类的泛型类型的名称SubFoo.如果我们做出一些改变,这将变得非常明显:

class SubFoo<String> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)

此行生成错误,T因为使用了未声明的类型.

然后,如果我们将行更改为:

class SubFoo<T> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)

我们回到了你原来的错误,'T'不符合'Hashable'.这里显而易见,因为T并不容易混淆现有Swift类型的名称恰好符合'Hashable'.很明显'T'是通用的.

当我们编写'String'时,它也只是泛型类型的占位符名称,而实际上并不是StringSwift中存在的类型.


如果我们想要一个特定类型的泛型类的不同名称,适当的方法几乎肯定是typealias:

class Foo<T:Hashable> {

}

typealias StringFoo = Foo<String>
Run Code Online (Sandbox Code Playgroud)

这是完全有效的Swift,它编译得很好.


如果我们想要的是实际上是子类并将方法或属性添加到泛型类,那么我们需要的是一个类或协议,它将使我们的泛型更具体到我们需要的东西.

回到原来的问题,让我们首先摆脱错误:

class Foo<T: Hashable>

class SubFoo<T: Hashable> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)

这是完全有效的Swift.但它对我们正在做的事情可能并不是特别有用.

我们不能做以下事情的唯一原因:

class SubFoo<T: String> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)

只是因为String不是Swift类 - 它是一个结构.任何结构都不允许这样做.


如果我们编写一个继承自的新协议Hashable,我们可以使用:

protocol MyProtocol : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyProtocol> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)

这完全有效.

另请注意,我们实际上不必继承自Hashable:

protocol MyProtocol { }
class Foo<T: Hashable> { }
class SubFoo<T: Hashable, MyProtocol> { }
Run Code Online (Sandbox Code Playgroud)

这也完全有效.

但请注意,无论出于何种原因,Swift都不允许您在这里使用课程.例如:

class MyClass : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyClass> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)

斯威夫特神秘地抱怨'T'不符合'Hashable'(即使我们添加了必要的代码来实现它.


最后,正确的方法和最适合Swift的方法将是编写一个继承自'Hashable'的新协议,并为您添加所需的任何功能.

我们的子类接受a不应该是非常重要的String.重要的是,无论我们的子类采用什么,它都具有我们所做的任何必要的方法和属性.


Jon*_*eet 16

I'm not a Swift developer, but having seen similar problems in Java, I suspect the problem is that at the moment you're declaring a type parameter called String because you're declaring class FooTranslator<String> - so the type argument in Translator<String> is just that type parameter, which has no constraints. You don't want a type parameter at all, I suspect (i.e. you don't want your FooTranslator to be a generic class itself.)

As noted in comments, in Swift subclasses of a generic class also have to be generic. You could possibly declare a throw-away type parameter, like this:

class FooTranslator<T>:Translator<String>
Run Code Online (Sandbox Code Playgroud)

which still avoids declaring a new type parameter called String, which was what was causing the problem. It means you're introducing a new type parameter when you don't want any type parameters, but it's possibly better than nothing...

This is all on the assumption that you really need a subclass, e.g. to add or override members. On the other hand, if you just want a type which is exactly the same as Translator<String>, you should use a type alias instead:

typealias FooTranslator = Translator<String>
Run Code Online (Sandbox Code Playgroud)

Or even mix the two in a horrible way, if you genuinely want a subclass but don't want to have to refer to it in a generic way:

class GenericFooTranslator<T>:Translator<String>
typealias FooTranslator = GenericFooTranslator<Int>
Run Code Online (Sandbox Code Playgroud)

(Note that the Int here is deliberately not String, to show that the T in Translator isn't the same as the T in FooTranslator.)