1. ホーム
  2. ios

[解決済み] 汎用プロトコルを変数の型として使用する方法

2023-02-03 19:18:34

質問

あるプロトコルがあるとします。

public protocol Printable {
    typealias T
    func Print(val:T)
}

そして以下はその実装です。

class Printer<T> : Printable {

    func Print(val: T) {
        println(val)
    }
}

私が期待していたのは、このように Printable 変数を使って、このような値を表示できるに違いないと思っていました。

let p:Printable = Printer<Int>()
p.Print(67)

コンパイラはこのエラーで文句を言っています。

プロトコル 'Printable'は、Selfまたは関連する型要件があるため、一般制約としてのみ使用することができます。 自己または関連する型の要件があるためです。

私は何か間違ったことをしているのでしょうか?どうすればいいですか?

**EDIT :** Adding similar code that works in C#

public interface IPrintable<T> 
{
    void Print(T val);
}

public class Printer<T> : IPrintable<T>
{
   public void Print(T val)
   {
      Console.WriteLine(val);
   }
}


//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)

EDIT 2: 私が欲しいものの実例です。これはコンパイルされませんが、私が達成したいことを提示していることに注意してください。

protocol Printable 
{
   func Print()
}

protocol CollectionType<T where T:Printable> : SequenceType 
{
   .....
   /// here goes implementation
   ..... 
}

public class Collection<T where T:Printable> : CollectionType<T>
{
    ......
}

let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
   item.Print()
}

どのように解決するのですか?

Thomas が指摘するように、型を全く与えないで変数を宣言することもできます(あるいは、明示的に型を与えることもできますが Printer<Int> . しかし、ここでは、なぜ型として Printable というプロトコルがあります。

関連する型を持つプロトコルを通常のプロトコルのように扱い、独立した変数型として宣言することはできません。 その理由を考えるために、次のようなシナリオを考えてみましょう。 ある任意の型を保存し、それを取り返すためのプロトコルを宣言したとします。

// a general protocol that allows for storing and retrieving
// a specific type (as defined by a Stored typealias
protocol StoringType {
    typealias Stored

    init(_ value: Stored)
    func getStored() -> Stored
}

// An implementation that stores Ints
struct IntStorer: StoringType {
    typealias Stored = Int
    private let _stored: Int
    init(_ value: Int) { _stored = value }
    func getStored() -> Int { return _stored }
}

// An implementation that stores Strings
struct StringStorer: StoringType {
    typealias Stored = String
    private let _stored: String
    init(_ value: String) { _stored = value }
    func getStored() -> String { return _stored }
}

let intStorer = IntStorer(5)
intStorer.getStored() // returns 5

let stringStorer = StringStorer("five")
stringStorer.getStored() // returns "five"

OK、ここまでは順調です。

さて、変数の型を実際の型ではなく、型が実装するプロトコルにする主な理由は、そのプロトコルに準拠した異なる種類のオブジェクトを同じ変数に代入し、オブジェクトが実際に何であるかに応じて実行時に多相な振る舞いを得られるようにするためです。

しかし、プロトコルが関連する型を持っている場合、これを行うことはできません。 以下のコードは実際にどのように動作するでしょうか?

// as you've seen this won't compile because
// StoringType has an associated type.

// randomly assign either a string or int storer to someStorer:
var someStorer: StoringType = 
      arc4random()%2 == 0 ? intStorer : stringStorer

let x = someStorer.getStored()

上のコードで x の型は何でしょうか? アン Int ? それとも String ? Swiftでは、すべての型はコンパイル時に固定されていなければなりません。 関数は、実行時に決定された要因に基づいて、1つの型を返すことから別の型に動的に移行することはできません。

その代わりに StoredType を一般的な制約として使うだけです。 例えば、あらゆる種類の保存された型を出力したいとします。 このような関数を書くことができます。

func printStoredValue<S: StoringType>(storer: S) {
    let x = storer.getStored()
    println(x)
}

printStoredValue(intStorer)
printStoredValue(stringStorer)

これは、コンパイル時に、コンパイラが2つのバージョンの printStoredValue に対して Int のためのもの、そして String s. その2つのバージョンの中で x は特定のタイプであることが知られています。