10

おそらく以前に尋ねられたかもしれませんが、これはすべてC ++の理解と認識の限界に近づいているので、何が話されているのか、正確に何が起こっているのかを理解するのが少し遅いです。コードに直接ジャンプしましょう。これは機能します:

template <typename T>
class Foo
{
    struct Bar
    {
        Bar() {}
        ~Bar() noexcept {}
        Bar(Bar&& b) : Bar() { swap(*this, b); }

        friend void swap(Bar& b1, Bar& b2) { /* ... */ }
    };
};

template class Foo<int>; // explicit instantiation of Foo with int type

swapしかし、構造体本体の外側に定義を移動するにはどうすればよいBarですか?私がこれを行う場合:

template <typename T>
class Foo {
    struct Bar {
        // ...
        Bar(Bar&& b) : Bar() { swap(*this, b); } // line 16
        // ...
        template <typename V>
          friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
    };
};

template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26

template class Foo<int>; // line 31

g ++(4.7.1、フラグ:-Wall -std = c ++ 11)レポート:

main.cpp: In instantiation of ‘Foo<T>::Bar::Bar(Foo<T>::Bar&&) 
            [with T = int; Foo<T>::Bar = Foo<int>::Bar]’:
main.cpp:31:16:   required from here
main.cpp:16:28: error: no matching function for call to 
            ‘swap(Foo<int>::Bar&, Foo<int>::Bar&)’
main.cpp:16:28: note: candidate is:
main.cpp:26:6: note: template<class T> void swap(typename Foo<T>::Bar&, 
                                                 typename Foo<T>::Bar&)
main.cpp:26:6: note:   template argument deduction/substitution failed:
main.cpp:16:28: note:   couldn't deduce template parameter ‘T’

swap明示的にインスタンス化するときにもコードを作成する必要があると思いますFooが、これは理にかなっていますが、コンパイラswap(Foo<int>::Bar&...)が作成する必要があることを理解できないのはなぜですか?テンプレートの置換が失敗するのはなぜですか?それとも私はすべてが間違っていますか?

更新1

と:

template <typename T> class Foo;
template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2);

template <typename T>
class Foo {
    struct Bar {
      Bar(Bar&& b) : Bar() { swap(*this, b); }  // line 19
      friend void swap<>(Foo<T>::Bar& b1, Foo<T>::Bar& b2); // line 20
    };
};

template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26

template class Foo<int>; // line 29

g ++(4.7.1、フラグ:-Wall -std = c ++ 11)レポート:

main.cpp: In instantiation of ‘struct Foo<int>::Bar’:
main.cpp:29:16:   required from here
main.cpp:20:17: error: template-id ‘swap<>’ for ‘void swap(Foo<int>::Bar&, Foo<int>::Bar&)’ does not match any template declaration
main.cpp: In instantiation of ‘Foo<T>::Bar::Bar(Foo<T>::Bar&&) [with T = int; Foo<T>::Bar = Foo<int>::Bar]’:
main.cpp:29:16:   required from here
main.cpp:19:24: error: no matching function for call to ‘Foo<int>::Bar::Bar()’
main.cpp:19:24: note: candidate is:
main.cpp:19:5: note: Foo<T>::Bar::Bar(Foo<T>::Bar&&) [with T = int; Foo<T>::Bar = Foo<int>::Bar]
main.cpp:19:5: note:   candidate expects 1 argument, 0 provided
main.cpp:19:28: error: no matching function for call to ‘swap(Foo<int>::Bar&, Foo<int>::Bar&)’
main.cpp:19:28: note: candidate is:
main.cpp:26:8: note: template<class T> void swap(typename Foo<T>::Bar&, typename Foo<T>::Bar&)
main.cpp:26:8: note:   template argument deduction/substitution failed:
main.cpp:19:28: note:   couldn't deduce template parameter ‘T’

更新2

OK、これはできません。Piotrはテンプレート内のネストされたクラスの出力にリンクしていますが、答えがわかりません。swap宣言の外で定義できないのはなぜですか?私が(誤)理解している限り、コンパイラがコードを作成swap(Foo<int>::Bar&...)して、コード内でリンクして、明示的にインスタンス化できないのはなぜFoo<int>ですか?私は何が起こっているのか完全に誤解しましたか?どうしたの?

更新3

OK、これはできません。テンプレートスペシャライゼーションがある場合、コンパイラは、特定のスペシャライゼーションではまったく異なる可能性があるため、swap外部で定義された呼び出しFooが明確であることを保証できないためです。Foo<some_class>::Bar私はそれが正しいことを願っています。しかし、明示的なインスタンス化を作成する前に、g ++がこれについて警告しないのはなぜFooですか?

template <typename T>
class Foo {
    struct Bar {
        // ...
        Bar(Bar&& b) : Bar() { swap(*this, b); }
        // ...
        template <typename V>
          friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
    };
};

template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {}

//template class Foo<int>; // let's comment this explicit instantiation out.

このコードは正常にコンパイルされます(g ++ 4.7.1、フラグ:-Wall -std = c ++ 11)。しかし、このコードが問題を引き起こす可能性があることを警告するべきではありませんか?の明示的なインスタンス化を追加するとFoo、問題はその行自体ではなく、のswap外部に実装されたコードにありFooます。

4

2 に答える 2

3

問題はではありませんfriend

問題は、この関数自体にあります。

template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26

ネストされたクラスからテンプレートパラメータを推測するTことはできません。参照:テンプレート内にネストされたクラスを出力するか、この回答をさらに改善してください:https ://stackoverflow.com/a/4092248/1463922

実行できない理由の例を示すために、次の関数について考えてみます。

template <class T>
void foo(typename A<T>::Bar);

そしてこのAの定義:

template <class T>
struct A { typedef int Bar; };

そしてこの呼び出し:

int a;
foo(a);

Tこの例では何ですか?それは、、または、 あまりにも、またはあなたが望むものであるintためです....問題は、どの関数を呼び出しているか、または...A<int>::BarintfloatA<float>::Barintfoo<int>(int)foo<float>(int)

または、質問にもっと近い例を示すために:

template <class T>
struct Foo {
   struct Bar {}; 
};

コンパイラはこれを解決するのに問題がないはずです:

template <class T>
void resolve(typename Foo<T>::Bar*);

しかし、ここでもコンパイラには問題があります。これは、他のクラスの特殊化が他のクラスの内部構造体を使用しないかどうかがわからないためです。

template <class T>
struct Foo<T*> {
   typedef Foo<T>::Bar Bar; 
};

だから:

Foo<void>::Bar b;
resolve(&b);

コンパイラは、呼び出すバージョンを知る機会がありません。

resolve<void>(Foo<void>::Bar*);
// or 
resolve<void*>(Foo<void>::Bar*);
//          ^   

何をアドバイスできますか-インラインフレンドを使用します-しかし、他のテンプレートクラスで実装します。これは機能しますが、これは少し過剰に設計されていると確信しています。

template <class S>
class ImplementSwap;

template <typename T>
class Foo {
    public:
    struct Bar {
        int a;
        Bar() {}
        ~Bar() {}
        friend class ImplementSwap<Foo<T>>;
        friend void swap(Foo<T>::Bar& b1, Foo<T>::Bar& b2)
        {  ImplementSwap<Foo<T>>::doSwap(b1, b2); }
        Bar(Bar&& b)  { swap(*this, b); }

    };
};

template <class T>
class ImplementSwap<Foo<T>> {
public:
   static void doSwap(typename Foo<T>::Bar&,typename Foo<T>::Bar&);
};

template <class T>
void ImplementSwap<Foo<T>>::doSwap(typename Foo<T>::Bar&,typename Foo<T>::Bar&) 
{
  // this one is not inline....
}

私はこのテストを行うためにBarを公開しました:

Foo<int>::Bar a = Foo<int>::Bar(); // move constructor

int main() {
  swap(a,a); // explicit swap
}

[古い]私の前の答えは完全に間違っていました、そして最初のコメントはそれを参照しています。

friend void swap<>(typename Foo<T>::Bar&, typename Foo<T>::Bar&);
//              ^^

[/年]

于 2012-10-13T18:54:51.483 に答える
1

私にとって、これは次のようになります。

template <typename T>
class Foo
{
    struct Bar
    {
        template <typename V>
        friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
    };
};

template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) { }

template class Foo<int>;

int main()
{
   Foo<int>::Bar  bb1, bb2;
   swap<int>(bb1, bb2);
}  

これはgccで実行されます:http://ideone.com/zZMYn

いくつかのメモ:

  1. クラス内のフレンド宣言は、クラス外でフレンドとして宣言されたオブジェクトの前方宣言を作成します。これは、フレンドクラス、関数、テンプレートに適用されます。これは、あなたの転送declがswap必要ないことを意味します。

  2. 構文some-name<..>は、テンプレートの特殊化とテンプレートのインスタンス化で使用されます。前方宣言(フレンド宣言を含む)および定義では使用されません。この例では、1つの前方宣言、1つの定義、および1つの呼び出し(関数のテンプレートのインスタンス化)があります。

関数swapをとして呼び出すとswap(bb1, bb2);、コンパイラはそれを見つけることができません。私にとって、上記の例のようにこの関数を呼び出す必要性swap<int>は、言語要件というよりもコンパイラーの問題です。コンパイラは、テンプレート関数呼び出しのテンプレートパラメータを推測する必要があります。

編集

上記の例では、varsbb1bb2タイプはFoo<int>::Barです。これは、のインスタンス化が次のようにswapなる必要があることを意味します。

void swap(Foo<int>::Bar &b1, Foo<int>::Bar &b2) { }

Foo<float>::Barsayは。とは異なるタイプであるため、他のインスタンス化はここでは機能しませんFoo<int>::Bar。に変換する方法はありませFoo<int>::BarFoo<float>::Bar。のテンプレートFoo<float>::Barがインスタンス化される場合のイベントは使用できません。パラメータの種類は異なります。

このテンプレートにいくつかの専門分野がある場合、状況はより複雑になる可能性があります。しかし、呼び出しの時点では、テンプレート自体しかありません。考慮されるためには、専門分野は呼び出しの時点で表示されている必要があります。

優れたコンパイラがこのケースを処理できる可能性があります。明示的な型の指定による回避策が利用可能であり、機能しているように見えるので、gccの現在の状態は完全にOKと言えます。

于 2012-10-13T19:35:01.390 に答える