Eine generische Funktion kann nicht explizit spezialisiert werden

91

Ich habe ein Problem mit folgendem Code:

func generic1<T>(name : String){
}

func generic2<T>(name : String){
     generic1<T>(name)
}

Das Ergebnis generic1 (name) führt zu einem Compilerfehler. "Eine generische Funktion kann nicht explizit spezialisiert werden."

Gibt es eine Möglichkeit, diesen Fehler zu vermeiden? Ich kann die Signatur der generic1-Funktion nicht ändern, daher sollte sie (String) -> Void sein

Greyisf
quelle
2
Was bringt es, hier einen generischen Typ zu verwenden, wenn er für den Kontext nicht abgeleitet werden kann? Wenn der generische Typ nur intern verwendet wird, sollten Sie den Typ im Hauptteil der Funktion angeben.
Kirsteins
Wird der Platzhaltertyp Tüberhaupt in verwendet generic1()? Wie würden Sie diese Funktion aufrufen , damit der Compiler auf den Typ schließen kann?
Martin R
3
Ich hoffe, dass es eine Möglichkeit gibt, eine Funktion wie genetisch1 <blaClass> ("SomeString")
aufzurufen
1
Generika sind nicht nur für Fälle gedacht, in denen der Compiler auf den Kontext schließen kann. Eine explizite Spezialisierung der Funktion würde es ermöglichen, auf andere Teile des Codes zu schließen. Beispiel: func foo<T>() -> T { ... }Das macht etwas mit einem Objekt tvom Typ Tund der Rückgabe t. Eine explizite Spezialisierung Twürde es ermöglichen t1, eingegriffen zu werden var t1 = foo<T>(). Ich wünschte, es gäbe auch eine Möglichkeit, Funktionen auf diese Weise aufzurufen. C # erlaubt es
nacho4d
1
Ich bin auch nur darauf gestoßen. Es macht keinen Sinn, eine generische Funktion zu erstellen, wenn Sie gezwungen sind, den Typ als Parameter zu übergeben. Das muss ein Fehler sein, oder?!
Nick

Antworten:

159

Ich hatte auch dieses Problem und fand eine Problemumgehung für meinen Fall.

In diesem Artikel hat der Autor das gleiche Problem

https://www.iphonelife.com/blog/31369/swift-programming-101-generics-practical-guide

Das Problem scheint also zu sein, dass der Compiler irgendwie auf den Typ von T schließen muss. Es ist jedoch nicht erlaubt, einfach generisches <Typ> (Parameter ...) zu verwenden.

Normalerweise kann der Compiler nach dem Typ von T suchen, indem er die Parametertypen scannt, da hier in vielen Fällen T verwendet wird.

In meinem Fall war es ein bisschen anders, weil der Rückgabetyp meiner Funktion T war. In Ihrem Fall scheint es, dass Sie T in Ihrer Funktion überhaupt nicht verwendet haben. Ich denke, Sie haben gerade den Beispielcode vereinfacht.

Ich habe also folgende Funktion

func getProperty<T>( propertyID : String ) -> T

Und zum Beispiel im Fall von

getProperty<Int>("countProperty")

Der Compiler gibt mir den Fehler:

Eine generische Funktion kann nicht explizit spezialisiert werden

Um dem Compiler eine weitere Informationsquelle zu geben, aus der er auf den Typ von T schließen kann, müssen Sie den Typ der Variablen, in der der Rückgabewert gespeichert ist, explizit deklarieren.

var value : Int = getProperty("countProperty")

Auf diese Weise weiß der Compiler, dass T eine ganze Zahl sein muss.

Insgesamt bedeutet dies einfach, dass Sie, wenn Sie eine generische Funktion angeben, mindestens T in Ihren Parametertypen oder als Rückgabetyp verwenden müssen.

ThottChief
quelle
2
Hat mir eine Menge Zeit gespart. Danke dir.
Chrislarson
3
Gibt es eine Möglichkeit, dies zu tun, wenn es keinen Rückgabewert gibt? dhfunc updateProperty<T>( propertyID : String )
Kyle Bashour
1
Ich verstehe nicht, warum Sie das tun wollen? Da Sie nichts zurückgeben, hat es keinen Vorteil, Ihre Funktion als generisch zu deklarieren.
ThottChief
@ThottChief gibt es viele Vorteile, zB wenn Sie Schlüsselpfade verwenden / erstellen oder Typnamen als Zeichenfolge usw. erhalten
Zaitsman
Tolle Antwort, danke. Ich wünschte, dies würde funktionieren, let value = foo() as? Typedamit es in einem verwendet werden könnte, ifoder guarddas Ergebnis ist optional, aber es ist nicht ...
agirault
52

Swift 5

Normalerweise gibt es viele Möglichkeiten, generische Funktionen zu definieren. Sie basieren jedoch auf einer Bedingung, Tdie als parameteroder als verwendet werden muss return type.

extension UIViewController {
    class func doSomething<T: UIView>() -> T {
        return T()
    }

    class func doSomethingElse<T: UIView>(value: T) {
        // Note: value is a instance of T
    }

    class func doLastThing<T: UIView>(value: T.Type) {
        // Note: value is a MetaType of T
    }
}

Danach müssen wir Tbeim Anruf angeben.

let result = UIViewController.doSomething() as UIImageView // Define `T` by casting, as UIImageView
let result: UILabel = UIViewController.doSomething() // Define `T` with property type, as UILabel
UIViewController.doSomethingElse(value: UIButton()) // Define `T` with parameter type, as UIButton
UIViewController.doLastThing(value: UITextView.self) // Define `T` with parameter type, as UITextView

Ref:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2
nahung89
quelle
Tolle Antwort auf das allgemeine Problem ... da es so viele Möglichkeiten gibt, es zu beheben. Ich habe auch festgestellt, dass es self.result = UIViewController.doSomething()von alleine funktioniert, solange Sie die Eigenschaft bei der Deklaration eingegeben haben.
Teradyl
tolle Antwort, die viele Dinge erklärt.
Okhan Okbay
Java doLastThing<UITextView>()wird zu Swift doLastThing(UITextView.self), was zumindest nicht das Schlimmste ist. Besser, als komplizierte Ergebnisse explizit eingeben zu müssen. Vielen Dank für die Problemumgehung.
Erhannis
18

Die Lösung verwendet den Klassentyp als Parameter (wie in Java).

Um dem Compiler mitzuteilen, mit welchem ​​Typ er es zu tun hat, übergeben Sie die Klasse als Argument

extension UIViewController {
    func navigate<ControllerType: UIViewController>(_ dump: ControllerType.Type, id: String, before: ((ControllerType) -> Void)?){
        let controller = self.storyboard?.instantiateViewController(withIdentifier: id) as! ControllerType
        before?(controller)
        self.navigationController?.pushViewController(controller, animated: true)
    }
}

Rufen Sie an als:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })
Orkhan Alikhanov
quelle
1
Das ist großartig und viel besser als die akzeptierte Antwort. Bitte ziehen Sie eine Bearbeitung in Betracht, um den historischen Teil zu entfernen, oder setzen Sie ihn zumindest unter die Lösung. Vielen Dank!
Dan Rosenstark
1
@ DanRosenstark Danke für das Feedback :)
Orkhan Alikhanov
Obwohl dies funktioniert, denke ich nicht, dass es die beste Praxis ist. Der Grund ist, dass die Funktionsdefinition jetzt Entscheidungen darüber trifft, was zu tun ist, wenn es ein Problem mit der Besetzung gibt. Hier stürzt man also nur ab, wenn die Besetzung fehlschlägt. Es ist besser, den Kunden entscheiden zu lassen, was zu tun ist, da dies je nach Kunde variieren kann. Also werde ich diese Antwort aus diesem Grund ablehnen.
smileBot
@smileBot Du meinst das Beispiel ist schlecht oder der Ansatz?
Orkhan Alikhanov
Die Vorgehensweise. Ich denke, es ist eine allgemeine Regel der Programmierung, die Spezialisierung zu verschieben, wenn dies praktikabel ist. Dies ist Teil der Idee, Verantwortung zu verstehen. Es liegt nicht in der Verantwortung der Funktion, das Generikum zu spezialisieren. Dies liegt in der Verantwortung des Anrufers, da die Funktion nicht wissen kann, was alle Anrufer benötigen. Wenn die Funktion in diese Rolle übergeht, wird die Verwendung der Funktion ohne Verstärkung eingeschränkt. Sie können dies auf viele Codierungsprobleme verallgemeinern. Dieses einzige Konzept hat mir enorm geholfen.
smileBot
4

Sie benötigen hier kein Generikum, da Sie statische Typen haben (String als Parameter). Wenn Sie jedoch eine generische Funktion haben möchten, rufen Sie eine andere auf. Sie können Folgendes tun.

Generische Methoden verwenden

func fetchObjectOrCreate<T: NSManagedObject>(type: T.Type) -> T {
    if let existing = fetchExisting(type) {
       return existing
    }
    else {
        return createNew(type)
    }
}

func fetchExisting<T: NSManagedObject>(type: T.Type) -> T {
    let entityName = NSStringFromClass(type)
     // Run query for entiry
} 

func createNew<T: NSManagedObject>(type: T.Type) -> T {
     let entityName = NSStringFromClass(type)
     // create entity with name
} 

Verwenden einer generischen Klasse (Weniger flexibel, da die generische Klasse nur für einen Typ pro Instanz definiert werden kann)

class Foo<T> {

   func doStuff(text: String) -> T {
      return doOtherStuff(text)
   }

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")
aryaxt
quelle
3

Ich denke, wenn Sie eine generische Funktion angeben, sollten Sie einige der Parameter vom Typ T wie folgt angeben:

func generic1<T>(parameter: T) {
    println("OK")
}

func generic2<T>(parameter: T) {
    generic1(parameter)
}

Wenn Sie die handle () -Methode aufrufen möchten, können Sie dies tun, indem Sie ein Protokoll schreiben und die Typbeschränkung für T angeben:

protocol Example {
    func handle() -> String
}

extension String: Example {
    func handle() -> String {
        return "OK"
    }
}

func generic1<T: Example>(parameter: T) {
    println(parameter.handle())
}

func generic2<T: Example>(parameter: T) {
    generic1(parameter)
}

Sie können diese generische Funktion also mit String aufrufen:

generic2("Some")

und es wird kompiliert

Valerii Lider
quelle
1

Bisher verwendete meine persönliche Best Practice die Antwort von @ orkhan-alikhanov. Heute, wenn sie bei SwiftUI suchen und wie .modifier()und ViewModifierimplementiert ist, fand ich eine andere Art und Weise (oder ist es eher eine Abhilfe?)

Wickeln Sie einfach die zweite Funktion in eine struct.

Beispiel:

Wenn dies der Fall ist, kann eine generische Funktion nicht explizit spezialisiert werden.

func generic2<T>(name: String){
     generic1<T>(name)
}

Dieser könnte helfen. Wickeln Sie die Erklärung von generic1in ein struct:

struct Generic1Struct<T> {
    func generic1(name: String) {## do, whatever it needs with T ##}
}

und nenne es mit:

func generic2<T>(name : String){
     Generic1Struct<T>().generic1(name: name)
}

Bemerkungen:

  • Ich weiß nicht, ob es in irgendeinem Fall hilft, wenn diese Fehlermeldung auftritt. Ich weiß nur, dass ich oft feststeckte, als das auftauchte. Ich weiß, dass diese Lösung heute geholfen hat, als die Fehlermeldung auftauchte.
  • Die Art und Weise, wie Swift mit Generika umgeht, ist für mich immer noch verwirrend.
  • Dieses Beispiel und die Problemumgehung mit structist ein gutes Beispiel. Die Problemumgehung hier hat nicht ein bisschen mehr Informationen - sondern übergibt den Compiler. Gleiche Informationen, aber unterschiedliche Ergebnisse? Dann stimmt etwas nicht. Wenn es sich um einen Compiler-Fehler handelt, kann er behoben werden.
jboi
quelle
0

Ich hatte ein ähnliches Problem mit meiner generischen Klassenfunktion class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Ich könnte es nicht nennen, let a = retrieveByKey<Categories>(key: "abc")wenn Categories eine Unterklasse von GrandLite ist.

let a = Categories.retrieveByKey(key:"abc")zurückgegeben GrandLite, nicht Kategorien. Generische Funktionen leiten den Typ nicht basierend auf der Klasse ab, die sie aufruft.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?gab mir einen Fehler, als ich versuchte, let a = Categories.retrieveByKey(aType: Categories, key: "abc")gab mir einen Fehler, dass Categories.Type nicht in GrandLite konvertiert werden konnte, obwohl Categories eine Unterklasse von GrandLite ist. JEDOCH...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? hat funktioniert, wenn ich let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")anscheinend versucht habe , dass eine explizite Zuweisung einer Unterklasse nicht funktioniert, aber eine implizite Zuweisung mit einem anderen generischen Typ (Array) funktioniert in Swift 3.

adamek
quelle
1
Um den Fehler zu beheben, müssen Sie aTypeals Instanz von angeben T, anstatt sich Categoriesselbst zu setzen . Bsp. : let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Eine andere Lösung ist definieren aType: T.Type. Dann rufen Sie die Methode alslet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89