Невозможно явно специализировать универсальную функцию


92

У меня проблема со следующим кодом:

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

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

результат generic1 (name) для ошибки компилятора "Невозможно явно специализировать универсальную функцию"

Есть ли способ избежать этой ошибки? Я не могу изменить сигнатуру функции generic1, поэтому она должна быть (String) -> Void


2
Какой смысл здесь использовать универсальный тип, если его нельзя вывести из контекста? Если универсальный тип используется только внутри, вы должны указать тип в теле функции.
Kirsteins

TИспользуется ли вообще тип заполнителя generic1()? Как бы вы вызывали эту функцию, чтобы компилятор мог определить тип?
Martin R

3
Я надеюсь, что есть способ вызвать такую ​​функцию, как генетический1 <blaClass> ("SomeString")
Грейисф,

2
Обобщения предназначены не только для случаев, когда компилятор может вывести контекст. Явная специализация функции позволит вводить другие части кода. func foo<T>() -> T { ... }пример : что-то делает с объектом tтипа Tи возвращением t. Явная специализация Tпозволила t1бы сделать вывод var t1 = foo<T>(). Я бы хотел, чтобы был способ вызывать функции таким же образом. C # позволяет это
nacho4d

1
Я тоже с этим столкнулся. Нет смысла создавать универсальную функцию, если вы вынуждены передавать тип в качестве параметра. Это должно быть ошибка?
Ник

Ответы:


161

У меня тоже была эта проблема, и я нашел способ ее решения.

В этой статье у автора та же проблема

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

Итак, проблема, похоже, в том, что компилятор должен каким-то образом вывести тип T. Но нельзя просто использовать общий <тип> (параметры ...).

Обычно компилятор может искать тип T, просматривая типы параметров, потому что именно здесь T используется во многих случаях.

В моем случае это было немного иначе, потому что тип возвращаемого значения моей функции был T. В вашем случае кажется, что вы вообще не использовали T в своей функции. Думаю, вы просто упростили пример кода.

Итак, у меня есть следующая функция

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

А в случае, например,

getProperty<Int>("countProperty")

компилятор выдает ошибку:

Невозможно явно специализировать универсальную функцию

Итак, чтобы предоставить компилятору другой источник информации, из которого можно сделать вывод о типе T, вы должны явно объявить тип переменной, в которой сохраняется возвращаемое значение.

var value : Int = getProperty("countProperty")

Таким образом, компилятор знает, что T должно быть целым числом.

Поэтому я думаю, что в целом это просто означает, что если вы указываете универсальную функцию, вы должны хотя бы использовать T в своих типах параметров или в качестве возвращаемого типа.


2
Сэкономил кучу времени. Спасибо.
Крисларсон

4
Есть ли способ сделать это, если нет возвращаемого значения? то есть,func updateProperty<T>( propertyID : String )
Кайл Башур

1
Я не понимаю, зачем вам это нужно? Поскольку вы ничего не возвращаете, объявлять функцию универсальной не имеет смысла.
ThottChief 08

@ThottChief дает много преимуществ, например, если вы используете / создаете пути для ключей или получаете имя типа в виде строки и т. Д.
zaitsman

Отличный ответ, спасибо. Я бы хотел, чтобы это работало, let value = foo() as? Typeчтобы его можно было использовать в a, ifиначе guardрезультат не является обязательным, но он не ...
agirault

54

Swift 5

Обычно существует множество способов определения общих функций. Но они основаны на условии, которое Tдолжно использоваться как parameterили как 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
    }
}

После этого мы должны предоставить Tпри звонке.

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

Ссылка:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2

Отличный ответ на общую проблему ... поскольку есть много способов исправить это. Я также понял, что self.result = UIViewController.doSomething()работает сам по себе, если вы ввели свойство при его объявлении.
терадил 05

отличный ответ, объясняющий много вещей.
Охан Окбай 07

Java doLastThing<UITextView>()становится Swift doLastThing(UITextView.self), что, по крайней мере, не самое худшее. Лучше, чем явно вводить сложные результаты. Спасибо за обходной путь.
Erhannis

18

Решение принимает тип класса в качестве параметра (как в Java)

Чтобы компилятор знал, с каким типом он имеет дело, передайте класс в качестве аргумента

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)
    }
}

Звоните как:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })

1
Это здорово и намного лучше, чем принятый ответ. Пожалуйста, рассмотрите возможность редактирования, чтобы удалить историческую часть или, по крайней мере, поместить ее под решением. Благодарность!
Дэн Розенстарк

1
@DanRosenstark Спасибо за отзыв :)
Орхан Алиханов

Хотя это работает, я не думаю, что это лучшая практика. Причина в том, что теперь определение функции принимает решение о том, что делать, если есть проблема с приведением. Итак, здесь вы просто рушитесь, если не работает кастинг. Лучше позволить клиенту решать, что делать, поскольку это может варьироваться в зависимости от клиента. Итак, я собираюсь проголосовать против этого ответа по этой причине.
smileBot

@smileBot Вы имеете в виду плохой пример или подход?
Орхан Алиханов

Подход. Я думаю, что общее правило программирования - откладывать специализацию, когда это возможно. Это часть идеи понимания ответственности. Специализация универсального типа не является обязанностью функции. Это ответственность вызывающего абонента, поскольку функция не может знать, что потребуется всем вызывающим абонентам. Если функция попадает в эту роль, это ограничивает использование функции без выгоды. Вы можете обобщить это на многие проблемы с кодированием. Эта единственная концепция мне очень помогла.
smileBot

4

Здесь вам не нужен общий тип, поскольку у вас есть статические типы (String в качестве параметра), но если вы хотите, чтобы общая функция вызывала другую, вы можете сделать следующее.

Использование общих методов

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
} 

Использование универсального класса (менее гибкий, поскольку универсальный можно определить только для 1 типа для каждого экземпляра)

class Foo<T> {

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

   func doOtherStuff(text: String) -> T {

   }  

}

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

3

Я думаю, что когда вы указываете универсальную функцию, вы должны указать некоторые параметры типа T, например:

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

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

и если вы хотите вызвать метод handle (), вы можете сделать это, написав протокол и указав ограничение типа для T:

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)
}

поэтому вы можете вызвать эту общую функцию с помощью String:

generic2("Some")

и он скомпилирует


1

До сих пор в моей личной передовой практике использовался ответ @orkhan-alikhanov. Сегодня, глядя на SwiftUI и как .modifier()и ViewModifierреализуются, я нашел еще один способ (или это скорее обходной путь?)

Просто оберните вторую функцию в struct.

Пример:

Если это дает вам сообщение «Невозможно явно специализировать универсальную функцию»

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

Это может помочь. Оберните объявление generic1в struct:

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

и назовите его:

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

Примечания:

  • Я не знаю, помогает ли это в любом случае, когда появляется это сообщение об ошибке. Я просто знаю, что много раз застревал, когда это всплывало. Я знаю, что это решение помогло сегодня, когда появилось сообщение об ошибке.
  • То, как Swift обрабатывает Generics, все еще сбивает меня с толку.
  • Этот пример и обходной путь с помощью structявляются хорошими примерами. Обходной путь здесь не содержит дополнительной информации, но передается компилятору. Та же информация, но разные результаты? Тогда что-то не так. Если это ошибка компилятора, ее можно исправить.

0

У меня была аналогичная проблема с моей функцией общего класса class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Я не мог назвать это let a = retrieveByKey<Categories>(key: "abc")там, где Категории - это подкласс GrandLite.

let a = Categories.retrieveByKey(key:"abc")вернул GrandLite, а не категории. Универсальные функции не определяют тип на основе класса, который их вызывает.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?дал мне ошибку, когда я попытался, let a = Categories.retrieveByKey(aType: Categories, key: "abc")дал мне ошибку, что он не может преобразовать Categories.Type в GrandLite, хотя Categories является подклассом GrandLite. ОДНАКО...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? действительно сработало, если я попробовал let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")явно явное присвоение подкласса, не работает, но неявное присвоение с использованием другого универсального типа (массива) действительно работает в Swift 3.


1
Чтобы исправить ошибку, вы должны предоставить aTypeкак экземпляр Tвместо самого Categoriesсебя. Пример: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Другое решение - определить aType: T.Type. Затем вызовите метод какlet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.