ジェネリック関数を明示的に特化することはできません


91

次のコードに問題があります:

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

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

generic1(名)コンパイラエラーの結果は、「明示的に汎用的な機能を特化することはできません」

このエラーを回避する方法はありますか?generic1関数のシグネチャを変更できないため、(String)-> Voidにする必要があります


2
ジェネリック型をコンテキストで推測できない場合、ここでジェネリック型を使用する意味は何ですか?ジェネリック型が内部でのみ使用される場合は、関数の本体内で型を指定する必要があります。
キルシュタイン

でプレースホルダータイプをT使用していgeneric1()ますか?コンパイラが型を推測できるように、その関数をどのように呼び出しますか?
マーティンR

3
Genetic1 <blaClass>( "SomeString")のような関数を呼び出す方法があることを願っています
Greyisf

1
ジェネリックスは、コンパイラーがコンテキストを推測できる場合だけのものではありません。関数を明示的に特化すると、コードの他の部分を推測できます。例:タイプのfunc foo<T>() -> T { ... }オブジェクトとreturn tで何かを行います。明示的に特化すると、で推論できるようになります。これらの方法で関数を呼び出す方法があったらいいのにと思います。C#で許可TtTt1var t1 = foo<T>()
nacho4d 2015

1
私もちょうどこれに遭遇しました。型をパラメーターとして渡す必要がある場合は、ジェネリック関数を作成しても意味がありません。これはバグでしょうか?!
Nick

回答:


160

私もこの問題を抱えており、私のケースの回避策を見つけました。

この記事では著者にも同じ問題があります

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

したがって、問題はコンパイラがTの型をどういうわけか推測する必要があることのようです。ただし、単純にgeneric <type>(params ...)を使用することはできません。

通常、ここではTが使用されるため、コンパイラーはパラメーターの型をスキャンすることにより、Tの型を探すことができます。

私の場合、関数の戻り値の型がTだったため、少し異なりました。あなたの場合、関数でTをまったく使用していないようです。サンプルコードを単純化したと思います。

だから私は次の機能を持っています

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

そして、例えば、

getProperty<Int>("countProperty")

コンパイラは私にエラーを出します:

ジェネリック関数を明示的に特化することはできません

したがって、Tの型を推測するための別の情報源をコンパイラーに提供するには、戻り値が保存される変数の型を明示的に宣言する必要があります。

var value : Int = getProperty("countProperty")

このようにして、コンパイラーはTが整数でなければならないことを認識します。

つまり、全体的には、ジェネリック関数を指定する場合、少なくともパラメーターの型でTを使用するか、戻り値の型として使用する必要があるということです。


2
時間を大幅に節約できました。ありがとうございました。
chrislarson、2015

3
戻り値がない場合にそれを行う方法はありますか?つまり、func updateProperty<T>( propertyID : String )
カイルバスフール

1
なぜそれをしたいのか分かりませんか?何も返さないため、ジェネリック関数を宣言してもメリットはありません。
ThottChief 2016年

@ThottChiefには多くの利点があります。たとえば、キーパスを使用/構築している場合、タイプ名を文字列として取得している場合など
zaitsman

素晴らしい回答ありがとうございます。これが動作するlet value = foo() as? Typeようにして、それをで使用できるようにするifguard、結果をオプションにすることはできますが、そうではありません...
agirault

53

スウィフト5

一般的に、ジェネリック関数を定義するには多くの方法があります。ただし、これらは、またはとしてT使用する必要がある条件に基づいています。parameterreturn 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()プロパティを宣言するときにプロパティを入力している限り、それ自体で動作することにも気付きました。
teradyl 2017

多くのことを説明する素晴らしい答え。
Okhan Okbay

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
これは素晴らしく、受け入れられた答えよりもはるかに優れています。編集して履歴部分を削除するか、少なくともソリューションの下に配置することを検討してください。ありがとう!
Dan Rosenstark 2017

1
@DanRosenstarkフィードバックをありがとう:)
Orkhan Alikhanov

これは機能しますが、ベストプラクティスではないと思います。その理由は、関数定義がキャストに問題がある場合の対処法を決定しているためです。したがって、キャストが失敗した場合、ここでクラッシュします。これはクライアントによって異なる可能性があるため、クライアントに何をするかを決定させるのが良いでしょう。だから、私はこの理由でこの回答を反対票で投票するつもりです。
smileBot

@smileBotあなたは例が悪いということですか、それともアプローチですか?
Orkhan Alikhanov

アプローチ。実用的な場合は専門化を延期することはプログラミングの一般的なルールだと思います。これは責任を理解するという考え方の一部です。ジェネリックを特化することは関数の責任ではありません。関数はすべての呼び出し元が必要とするものを知ることができないため、これは呼び出し元の責任です。関数がこの役割に入ると、関数の使用が制限されて利益が得られません。これを多くのコーディング問題に一般化できます。この単一のコンセプトは私に非常に役立ちました。
smileBot

4

静的型(パラメーターとしての文字列)があるため、ここではジェネリックは必要ありませんが、ジェネリック関数を別の関数で呼び出したい場合は、次のようにします。

ジェネリックメソッドの使用

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実装されて、私は別の方法を発見した(または、より回避策です?)

2番目の関数をにラップするだけ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")Categoriesが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")てくれ、それはカテゴリーがGrandLiteのサブクラスであっても、GrandLiteにCategories.Typeを変換することができなかったというエラーが発生しました。しかしながら...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? やった、私がしようとした場合、作業をlet a = Categories.retrieveByKey(aType: [Categories](), key: "abc")明らかに動作しないサブクラスの明示的な割り当てを、しかし、別のジェネリック型(配列)を使用して、暗黙のassigmentはスウィフト3で作業を行います。


1
エラーを修正するには、自分自身を置くのaTypeTはなく、のインスタンスとして提供する必要がありますCategories。例:let a = Categories.retrieveByKey(aType: Categories(), key: "abc")。別の解決策はdefine aType: T.Typeです。次に、メソッドを次のように呼び出しますlet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
-nahung89
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.