SwiftではC++のテンプレートのようなダックタイピングはできない？


C++で下記のコードは動くことが確認できました。（実験的というか動作を確認するコードなので実用性はありません）

[code]

    #include <iostream>
    
    template<typename T>
    class Sample {
    public:
      Sample(T val) {
        val.bar();
      }
    };
    
    class Hoge {
    public:
      void bar() {
        std::cout << "AAA" << std::endl;
      }
    };
    
    class Foo {
    public:
      void bar() {
        std::cout << "BBB" << std::endl;
      }
    };
    
    int main() {
      Sample<Hoge> h = Sample<Hoge>(*new Hoge());
      Sample<Foo> f = Sample<Foo>(*new Foo());
    
      return 0;
    }
    
[/code]

結果：

[code]

    AAA
    BBB
    
[/code]

  
同様のことをSwiftでやってみようと思って下記のコードを記述しました。

[code]

    import Foundation
    
    class Sample<T> {
      init(val: T) {
        val.bar()
      }
    }
    
    class Hoge {
      func bar() {
        print("AAA")
      }
    }
    
    class Foo {
      func bar() {
        print("BBB")
      }
    }
    
    let h = Sample<Hoge>(val: Hoge())
    let f = Sample<Foo>(val: Foo())
    
[/code]

エラーになってしまいます：

[code]

    error: MyPlayground.playground:5:9: error: value of type 'T' has no member 'bar'
        val.bar()
        ~~~ ^~~
    
[/code]

  
仕方がないので、Protocolを導入しました。

[code]

    import Foundation
    
    protocol Barable {
      func bar()
    }
    
    class Sample<T: Barable> {
      init(val: T) {
        val.bar()
      }
    }
    
    class Hoge: Barable {
      func bar() {
        print("AAA")
      }
    }
    
    class Foo: Barable {
      func bar() {
        print("BBB")
      }
    }
    
    let h = Sample<Hoge>(val: Hoge())
    let f = Sample<Foo>(val: Foo())
    
[/code]

結果：

[code]

    AAA
    BBB
    
[/code]

動きました。  
とはいえ、これでは、もうジェネリクスを使わないで、下記のようにしてしまったほうがわかりやすいです。

[code]

    import Foundation
    
    protocol Barable {
      func bar()
    }
    
    class Sample {
      init(val: Barable) {
        val.bar()
      }
    }
    
    class Hoge: Barable {
      func bar() {
        print("AAA")
      }
    }
    
    class Foo: Barable {
      func bar() {
        print("BBB")
      }
    }
    
    let h = Sample(val: Hoge())
    let f = Sample(val: Foo())
    
[/code]

蛇足ですが、JavaとDartでも試しましたが、やはりダメなようです。  
C++のテンプレートは他の言語比べてゆるめ（？）なのでしょうか？




C++ のテンプレートは、テンプレート引数に依存する部分の解釈は実際に引数が当てはめられるまで遅延するという規則です。
つまり、提示された例で言えばクラステンプレート `Sample` を定義した時点では `T` がどのようなメンバを持つのか検証しません。
テンプレート引数を当てはめたときに辻褄が合わないところがあればそのときになってエラーを通知します。 Two phase name lookup
と呼ばれる規則なので細かいことはウェブ検索してみてください。

実際にあてはめるまでテンプレート引数の性質がわからないというのは問題を引き起こしやすいことがわかっていて、 C++20 でようやく制約
(constraint) の概念が導入されましたがこれもテンプレート引数をあてはめたときにチェックをする仕組みなので Swift などとは根本的に違います。

検証のタイミングの違いなのでこれを「緩い」とみなすかどうかは考え方次第ですが、定義時点ではチェックが緩いという意味では緩いと言えるでしょう。




C++ のテンプレートは、テンプレート引数に依存する部分の解釈は実際に引数が当てはめられるまで遅延するという規則です。
つまり、提示された例で言えばクラステンプレート `Sample` を定義した時点では `T` がどのようなメンバを持つのか検証しません。
テンプレート引数を当てはめたときに辻褄が合わないところがあればそのときになってエラーを通知します。 Two phase name lookup
と呼ばれる規則なので細かいことはウェブ検索してみてください。

実際にあてはめるまでテンプレート引数の性質がわからないというのは問題を引き起こしやすいことがわかっていて、 C++20 でようやく制約
(constraint) の概念が導入されましたがこれもテンプレート引数をあてはめたときにチェックをする仕組みなので Swift などとは根本的に違います。

検証のタイミングの違いなのでこれを「緩い」とみなすかどうかは考え方次第ですが、定義時点ではチェックが緩いという意味では緩いと言えるでしょう。

