5399


以下の 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() { }` は期待どおりに機能します。


CC BY-SA 3.0
2015-01-27T01:45:57.490
5381
2015-01-29T10:14:02.053
2015-01-27T09:20:07.483
4978
7844
question
c++ templates
内部クラスの template 関数が解決されない


templateクラスの場合、template引数が違うクラスは、別クラス扱いになってしまうためです。つまり、`OuterClass<foo>::InnerClass`に`OuterClass<bar>::InnerClass`を代入しようとするのは、

[code]

    class OuterFoo{
        class Inner{
        };
    };
    class OuterBar{
        class Inner{
        };
    };
    
[/code]

と定義されている場合に、`OuterFoo::Inner`に`OuterBar::Inner`を代入しようとしているのと同じことです。

InnerClassがtemplate引数に非依存なのであれば、外部クラスとしてしまってはどうでしょうか？  
（01/27 11:13 コードを間違えていたので、修正）

[code]

    class OutOfInnerClass{
        OutOfInnerClass(OutOfInnerClass& _a_InnerClass_) {  }
    };
    template<typename _t_type_>
    class OuterClass {
    public:
        typedef OutOfInnerClass InnerClass;
    };
    
    template<typename _t_type_>
    class OuterClass<_t_type_>::InnerClass
    {
    public:
        typedef OutOfInnerClass InnerClass;
    };
    
    struct foo { };
    struct bar { };
    
    int main()
    {
        OuterClass<foo>::InnerClass o1;
        OuterClass<bar>::InnerClass o2 = o1;
    
    }
    
[/code]


CC BY-SA 3.0
2015-01-27T02:09:05.953
5386
2015-01-27T02:09:05.953
7344
5381
answer


ネストしている型はテンプレートの解決時に推論できないことがあります。例えば、上記のコードに、以下のコードが続いていると考えてください。

[code]

    struct baz {};
    
    template<>
    class OuterClass<baz> {
    public:
        typedef OuterClass<foo>::InnerClass InnerClass;
    };
    
[/code]

この場合、`OuterClass<foo>::InnerClass`と`OuterClass<baz>::InnerClass`は同じ型になりますので、`InnerClass`のテンプレートコピーコンストラクタが呼び出された時に、`_t_type2_`を`foo`と解決すべきか`baz`と解決すべきかコンパイラには判断できません。

このため、提示されたコードはコンパイルできないのです。解決方法は、何をしたいかによって異なると思います。

追記:  
以下の様なコードでは、`_t_type2_`は、`foo`になるべきでしょうか。それとも`baz`になるべきでしょうか。

[code]

    OuterClass<foo>::InnerClass o1;
    const OuterClass<baz>::InnerClass& o3 = o1;
    OuterClass<bar>::InnerClass o2 = o3;
    
[/code]


CC BY-SA 3.0
2015-01-27T02:49:54.253
5391
2015-01-27T03:15:57.960
2015-01-27T03:15:57.960
3066
3066
5381
answer


基底クラスからの継承ではダメなんですか？  
このようなクラス設計にしなければならない理由を知りたいです。

[code]

    class ParentClass{
    public:
        ParentClass(){}
    };
    
    template<typename T>
    class ChildClass : public ParentClass {
    public:
        ChildClass(){}
        ChildClass(const ParentClass &parentClass){}
    };
    
    struct foo { };
    struct bar { };
    
    int main(void)
    {
        ChildClass<foo> i1;
        ChildClass<bar> i2 = i1;
    
        return 0;
    }
    
[/code]


CC BY-SA 3.0
2015-01-27T02:56:56.533
5394
2015-01-27T02:56:56.533
7214
5381
answer


> 質問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_.
>   * [...]
>


CC BY-SA 3.0
2015-01-27T04:20:14.483
5399
2015-01-29T10:14:02.053
2015-01-29T10:14:02.053
49
49
5381
answer


代替案は既に他の方が回答していますので脇に置きつつ。  
ISO/IEC 14882:2014 を見直していたら、以下の記述を見付けました。  
テンプレート引数の推論のコンテキストに現れないテンプレート引数は推論の対象とならない、ということで例が挙げられています。  
この記述は、14882:2003, 14882:2014にも同様に存在します。

14.8.2.5 Deducing template arguments from a type p16 の Note:

> [ Note: Template parameters do not participate in template argument
> deduction  
>  if they are used only in non-deduced contexts. For example,
[code]

>     template<int i, typename T>
>     T deduce(typename A<T>::X x, // T is not deduced here
>       T t, // but T is deduced here
>       typename B<i>::Y y); // i is not deduced here
>     A<int> a;
>     B<77> b;
>     int x = deduce<77>(a.xm, 62, b.ym);
>     // T is deduced to be int, a.xm must be convertible to
>     // A<int>::X
>     // i is explicitly specified to be 77, b.ym must be convertible
>     // to B<77>::Y
>  
[/code]

>
> —end note ]

この例の `deduce` の第一引数 `typename A<T>::X x` が正に今回の例に当たると思います。  
これが推論対象ではないのは、既出ですが、 14.8.2.5 p8
で例示されているいずれのパターンにも属さないからでしょうかね。正確なところはちょっと自信がありません。

蛇足かもしれませんが、 14.8.2.5 p8 で、型推論できるパターンの列挙部を引用します。  
ここで、Tはテンプレートの型引数、TTはテンプレートのテンプレート型引数、iは型ではないテンプレート引数を表します。

> T  
>  _cv-list_ T  
>  T*  
>  T&  
>  T&&  
>  T[ _integer-constant_ ]  
>  _template-name_ <T> (where template-name refers to a class template)  
>  _type_ (T)  
>  T()  
>  T(T)  
>  T _type_ ::*  
>  type T::*  
>  T T::*  
>  T ( _type_ ::* )()  
>  _type_ (T::* )()  
>  _type_ ( _type_ ::* )(T)  
>  _type_ (T::*)(T)  
>  T ( _type_ ::* )(T)  
>  T (T::* )()  
>  T (T::* )(T)  
>  _type_ [i]  
>  _template-name_ <i> (where _template-name_ refers to a class template)  
>  TT<T>  
>  TT<i>  
>  TT<>

_template-name_ <T> まではありますが、typename _template-name_ <T>:: _type_ はないようです。  
引用元は 14882:2014で、2011も同じ。2003は右辺値参照がないことと、関数型がポインタになっている違いがありました。


CC BY-SA 3.0
2015-01-27T13:11:50.543
5435
2015-01-27T14:52:33.710
2015-01-27T14:52:33.710
7831
7831
5381
answer