内部クラスの template 関数が解決されない


以下の C++ ソースコードにおいて

[code]

    template<typename _t_type_>
    class OuterClass {
    public:
        class InnerClass;
    };
    
    template<typename _t_type_>
    class OuterClass<_t_type_>::InnerClass
    {
    public:
        InnerClass() { }
    
        template<typename _t_type2_>
        InnerClass(const typename OuterClass<_t_type2_>::InnerClass& _a_InnerClass_) {  }
    };
    
    struct foo { };
    struct bar { };
    
    int main()
    {
        OuterClass<foo>::InnerClass o1;
        OuterClass<bar>::InnerClass o2 = o1; // error: no viable conversion from 'OuterClass<foo>::InnerClass' to 'OuterClass<bar>::InnerClass'
    }
    
[/code]

`OuterClass<bar>::InnerClass o2 = o1;` がエラーとなります。

[code]

        template<typename _t_type2_>
        InnerClass(const typename OuterClass<_t_type2_>::InnerClass& _a_InnerClass_) {  }
    
[/code]

において `_t_type_` が `bar`, `_t_type2_` が `foo`
となって解決されることを期待したのですが、期待通りではありませんでした。 clang++, g++ の両方でコンパイルしたのですが同じ結果でした。

やりたいことは任意の `typename T` に対し

[code]

        OuterClass<T>::InnerClass
    
[/code]

となって、 任意の `typename T1` と任意の `typename T2` で

[code]

        OuterClass<T1>::InnerClass　と　OuterClass<T2>::InnerClass
    
[/code]

のコピーコンストラクタが成り立つようにしたいためです。

  * **質問1** : なぜこれは解決できないでエラーとなるのでしょうか。
  * **質問2** : エラーがでないようにするには、どうしたらいいでしょうか。

参考までに

[code]

        OuterClass<bar>::InnerClass o2 = o1
    
[/code]

をコメントアウトすることで、 `InnerClass() { }` は期待どおりに機能します。




> 質問2： エラーがでないようにするには、どうしたらいいでしょうか。  
>  コメントより： InnerClass同士に制限したい

「`OuterClass<??>::InterClass`同士のみ相互代入を許容する」という目的であれば、下記コードのように"マーカー"(`magic_type_for_innerclass`)を用意することで実現可能です。関数テンプレートへのデフォルト引数を使っているため、C++11以降が必要です。

[code]

    #include <type_traits>
    
    template<typename _t_type_>
    class OuterClass {
    public:
        class InnerClass;
    };
    
    template<typename _t_type_>
    class OuterClass<_t_type_>::InnerClass
    {
    public:
        typedef void magic_type_for_innerclass;
        InnerClass() { }
    
        // コンストラクタ
        template<
            typename U,
            typename = typename U::magic_type_for_innerclass
        >
        InnerClass(const U& rhs) {  }
    
        // 代入演算子
        template<
            typename U,
            typename = typename U::magic_type_for_innerclass
        >
        InnerClass& operator=(const U& rhs) { return *this; }
    };
    
    struct foo { };
    struct bar { };
    
    int main()
    {
        OuterClass<foo>::InnerClass o1;
        OuterClass<bar>::InnerClass o2 = o1;
        o1 = o2;
    
    //  無関係なクラス間代入はエラー
    //  OuterClass<bar>::InnerClass o3 = foo{};
    //  o3 = foo{};
    }
    
[/code]

おまけ：[テンプレート関数ではコピーコンストラクタ／代入演算子とならない](http://cpplover.blogspot.jp/2011/07/blog-
post_24.html)ため、ここではコピー"風"コンストラクタ／代入演算子という感じになりますね。

* * *

> 質問１： なぜこれは解決できないでエラーとなるのでしょうか。

感覚的な説明では「OuterクラスとInnerクラス同士では直接的な関係をもっていないため、Innerクラス実引数からOuterクラスを推論することは不可能」くらいでしょ‌​うか。

~~厳密な説明では、C++11言語仕様の§14.8.2.5 [temp.deduct.type] / Paragraph 8
で実引数から型推論のパターン列挙していますが、ここで`Outer<T>::Inner`からテンプレート型パラメータ`T`を推論するルールが定義されないため。となる気がします（正直、自信はありません…）。~~

このコンパイラ動作の根拠（C++言語仕様）は、「Outerクラス部分はテンプレート型推論の対象外となるから」という理由のようです。（thanks to
@MitsuruKariya さんコメント）

質問コードに適用すると _qualified-id_ ＝`OuterClass<T>::InnerClass`、 _nested-name-
specifier_ ＝`OuterClass<T>::`となり、テンプレート型パラメータ`T`はnon-deduced
contextsにあるため、型推論が行われないという結果となります。簡単には「最終`::`の右側でしか型推論されない」とみなせばOKです。

C++11言語仕様§14.8.2.5 [temp.deduct.type] / Paragraph 5より引用：

> 5 The non-deduced contexts are:
>
>   * The _nested-name-specifier_ of a type that was specified using a
> _qualified-id_.
>   * [...]
>




> 質問2： エラーがでないようにするには、どうしたらいいでしょうか。  
>  コメントより： InnerClass同士に制限したい

「`OuterClass<??>::InterClass`同士のみ相互代入を許容する」という目的であれば、下記コードのように"マーカー"(`magic_type_for_innerclass`)を用意することで実現可能です。関数テンプレートへのデフォルト引数を使っているため、C++11以降が必要です。

[code]

    #include <type_traits>
    
    template<typename _t_type_>
    class OuterClass {
    public:
        class InnerClass;
    };
    
    template<typename _t_type_>
    class OuterClass<_t_type_>::InnerClass
    {
    public:
        typedef void magic_type_for_innerclass;
        InnerClass() { }
    
        // コンストラクタ
        template<
            typename U,
            typename = typename U::magic_type_for_innerclass
        >
        InnerClass(const U& rhs) {  }
    
        // 代入演算子
        template<
            typename U,
            typename = typename U::magic_type_for_innerclass
        >
        InnerClass& operator=(const U& rhs) { return *this; }
    };
    
    struct foo { };
    struct bar { };
    
    int main()
    {
        OuterClass<foo>::InnerClass o1;
        OuterClass<bar>::InnerClass o2 = o1;
        o1 = o2;
    
    //  無関係なクラス間代入はエラー
    //  OuterClass<bar>::InnerClass o3 = foo{};
    //  o3 = foo{};
    }
    
[/code]

おまけ：[テンプレート関数ではコピーコンストラクタ／代入演算子とならない](http://cpplover.blogspot.jp/2011/07/blog-
post_24.html)ため、ここではコピー"風"コンストラクタ／代入演算子という感じになりますね。

* * *

> 質問１： なぜこれは解決できないでエラーとなるのでしょうか。

感覚的な説明では「OuterクラスとInnerクラス同士では直接的な関係をもっていないため、Innerクラス実引数からOuterクラスを推論することは不可能」くらいでしょ‌​うか。

~~厳密な説明では、C++11言語仕様の§14.8.2.5 [temp.deduct.type] / Paragraph 8
で実引数から型推論のパターン列挙していますが、ここで`Outer<T>::Inner`からテンプレート型パラメータ`T`を推論するルールが定義されないため。となる気がします（正直、自信はありません…）。~~

このコンパイラ動作の根拠（C++言語仕様）は、「Outerクラス部分はテンプレート型推論の対象外となるから」という理由のようです。（thanks to
@MitsuruKariya さんコメント）

質問コードに適用すると _qualified-id_ ＝`OuterClass<T>::InnerClass`、 _nested-name-
specifier_ ＝`OuterClass<T>::`となり、テンプレート型パラメータ`T`はnon-deduced
contextsにあるため、型推論が行われないという結果となります。簡単には「最終`::`の右側でしか型推論されない」とみなせばOKです。

C++11言語仕様§14.8.2.5 [temp.deduct.type] / Paragraph 5より引用：

> 5 The non-deduced contexts are:
>
>   * The _nested-name-specifier_ of a type that was specified using a
> _qualified-id_.
>   * [...]
>

