質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.54%

Cannot assign value of type 'A' to type 'T'と出るが理解ができない

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,078

solty_919

score 7

以下ソースコードがなぜダメなのかが理解できません。

protocol Sample {
    var hoge: String { get }
}
struct A: Sample {
    let hoge = "1"
    let piyo = "1"
}
struct B: Sample {
    let hoge = "2"
    let piyo = "2"
}
struct Person<T: Sample> {
    let name: String
    let fuga: T
    init(name: String) {
        self.name = name
        if name.isEmpty {
            self.fuga = A()   //ここでエラー
        } else {
            self.fuga = B()   //ここでエラー
        }
    }
}

エラーメッセージは以下です

Cannot assign value of type 'A' to type 'T'


T型じゃないから入れられないよって意味だと思うんですがTはSampleプロトコルに準拠していれば
入れれるはずです。AはSampleプロトコルに準拠しているので入るのが僕の考えなのですがどこか間違っているでしょうか?

ちなみにXcodeに任せてコード補完してもらうと

self.fuga = B() as! T


になります

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+3

Sampleプロトコルに準拠していれば入れれるはずです

質問者さんはTの型の条件を

(A)「Sampleプロトコルと互換性がある型ならなんでもよい」

と捉えておられると思います。それはある面では正しいですが、注意しなければならないのは(A)が

「Personを利用する側が型パラメータTに指定する具象型の条件」

であることです。Personの実装内部においてはTの制約は(A)だけでは不十分でして、そこでは

(B)「Personが用いられている文脈においてTに実際に指定された具象型と互換性がある」

という制約が必要です。次のコードを考えてみてください。

例1:

let pb: Person<B> = new Person(name: "")

このコードの文脈ではTがBである前提で解釈されます。しかしコンストラクターの中でAのインスタンスをfugaに無条件に代入できたとすると(A)は満たしますが(B)を満たしません。既にお気づきと思いますが「AはBと互換性がない型」だからです。


Personの中でSample型と互換性のある特定の具象型(A, B, etc.)をfugaへ代入したい場合genericsにしないほうがよいと思います。そういうケースではfugaの型は単にSampleでよいのではないでしょうか?

struct Person {
    let name: String
    let fuga: Sample

    init(name: String) {
        self.name = name
        if name.isEmpty {
            self.fuga = A()
        } else {
            self.fuga = B()
        }
    }
}

genericsとして定義する意味は例えば

let pb : Sample<B> = ...
let b = pb.fuga

このように書いたとき、bが「B型でなければならない」と解釈させたい場合に出てきます。このとき、型引数T型の具体的な値をPersonのメソッド内で得たい場合、普通「メソッドの利用者から引数で指定させる」ことが多いと思います。例えばコンストラクターなら

init(name: String, fuga: T)

のようにしておきます。Tを何型として扱いたいかがわかっているのはPersonを利用する側なわけですからPerson内部では特定の具象型を仮定することなく、利用者から与えてもらう方が分かり易いと思います。


もしどうしてもコンストラクターの中でA, Bなどの具象型の値をfugaに設定したいのであれば、Personの利用者がTを何型と仮定しているかわからない以上、「実際にTに指定された型と互換性があるかどうかをチェック」しなければなりません。そのチェックがすなわちA() as! Tということになります。

なおas!を用いて矛盾があるコード(前述の例1)を実行してみますと、期待通り

Could not cast value of type 'test.A' (0x7f6e6e4b5148) to 'test.B' (0x7f6e6e4b51b8).

という実行時エラーがおきてくれます。
(Ubuntu Swift 4.2でやってみました)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/26 13:49

    丁寧なご回答ありがとうございました!!
    「外からTを指定された時に中のTが必ず一致するとは限らない」
    という認識で理解しました。

    ご指摘の通り外からfugaの型をSampleにしたいところです。
    が希望として`Person.fuga.piyo`のようにアクセスして値を取り出したかったので
    ジェネリクスを使用していました。現状打開策が見当たらずなのでクラスを使って対処していますが
    なるべく継承はしたくありません。
    structとprotocolで希望が解決される手法がないか再度検討してみます。

    キャンセル

  • 2018/11/26 20:38

    元のご質問の範囲を超えてしまいますが...
    例に挙げておられるA, BをみるとSampleはhogeだけでなくpiyoというメンバーも持つものと期待しているように見えます。もしそうなら最もシンプルな解決法は
    protocol Sample {
     var hoge: String { get }
     var piyo: String { get }
    }
    とし、Person.fugaの型をTではなくSampleにすること(Personをgenericsでなくすこと)だと思います。

    > なるべく継承はしたくありません

    この意味がSampleにpiyoを定義したくないという意味ならA, Bの定義例は
    class A: Person {
     let hoge = "1"
     let foo = "1"
    }
    class B: Person {
     let hoge = "2"
     let bar = "2"
    }
    とし、
    let personA = Person<A>("")
    let fooOfPersonA = personA.fuga.foo
    let personB = Person<B>("a")
    let barOfPersonB = personB.fuga.bar
    のように利用したい

    と書かないと閲覧者に「何がしたいのか」伝わりにくいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.54%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる