0

カスタムMatrixクラスにコピーアンドスワップイディオムを実装しようとしていたところ、リンク先の質問で提案されている方法でswap()の実装に問題が発生しました。

(私が使用したコンパイラはMS VS2010 IDEのものであり、方言は古き良きC ++ 03です。)

// matrix.h

namespace my_space 
{

template<typename T> class Matrix
{
public:
    /* ... */

    friend void swap(Matrix<T> &first, Matrix<T> &second)
    {
        using std::swap;
        swap(first.width_, second.width_);
        swap(first.height_, second.height_);
        swap(first.data_, second.data_);
    }
};

} // namespace

この名前空間にある関数のコードで通常のstd::swap()に到達するのに問題があります。

// some_code.cpp:
#include "matrix.h"
#include <algorithm>

using namespace my_space;
using namespace std;

// SomeClass is part of my_space!
void SomeClass::some_function()
{
    int a = 3, b = 7;
    swap(a,b);  // I wan't std::swap!
}

残念ながら、何らかの理由でmy_space::swap()、Matrixは他のすべての呼び出しをエイリアスしているようstd::swap()です。引数が適合せず、ADLが優先する必要があるため、理由がわかりませんstd::swap

1>f:\src\some_code.cpp(653): error C3767: 'swap': candidate function(s) not accessible
1>          could be the friend function at 'f:\src\matrix.h(63)' : 'swap'  [may be found via argument-dependent lookup]

(エラーは、使用しようとしているすべての行で10回繰り返されますstd::swap

引数が合わない場合でも、my_space::swap()常にで却下std::swap()しますか?my_space表示されていないわけではなく、作成std::swap()my_space::swap()は問題なく動作していました。

4

4 に答える 4

1

STLコンテナが採用するアプローチでは、メンバー関数を使用してから、静的関数をオーバーロードします。例えば:

template<class T, class Alloc=std::allocator<T> >
class vector
{
   T *data;
   size_t n;
   size_t max_n;
public:
   void swap(vector<T, Alloc> &other)
   {
      swap(this->data, other.data);
      swap(this->n, other.n);
      swap(this->max_n, other.max_n);
   }
};

template<class T, class A>
void swap(vector<T, A> &lhs, vector<T, A> &rhs)
{
   lhs.swap(rhs);
}

提案されたMatrixクラスでは、単に同じアプローチを取ります...

namespace my_space
{
template<typename T>
class Matrix
{
   unsigned width_;
   unsigned height_;
   std::vector<T> data_;
public:
   void swap(Matrix<T> &other)
   {
      std::swap(this->width_, other.width_);
      std::swap(this->height_, other.height_);
      std::swap(this->data_, other.data_);  // calls this->data_.swap(other.data_);
   }
};
}

namespace std
{
   template<typename T>
   void swap(my_space::Matrix<T> &lhs, my_space::Matrix<T> &rhs)
   {
      lhs.swap(rhs);
   }
}
于 2012-07-20T03:37:24.397 に答える
1

コードがあなたが投稿したものと本当に似ている場合、それはコンパイラの問題です。コードは、必要に応じてclang++で正常にコンパイルされます。

フレンド宣言は、名前空間スコープを持つ関数を宣言するという点で奇妙ですが、宣言はADLを介してのみ使用でき、それでも、引数の少なくとも1つがフレンド宣言を持つクラスのタイプである場合にのみ使用できます。名前空間レベルの宣言がない限り、関数は名前空間スコープで使用できません。


テスト1 (明示的な宣言がない場合、名前空間レベルでは関数を使用できません):

namespace A {
   struct B {
      friend void f();  // [1]
   };
   // void f();         // [2]
}
void A::f() {}          // [3]

void A::f()[1]に、のフレンドとして宣言するフレンド宣言を追加しA::Bます。名前空間レベルで[2]に追加の宣言がないと、[3]の定義はコンパイルに失敗します。これは、A名前空間の外にあるため、その定義も自己宣言ではないためです。


Matrix<T>ここでの意味は、関数は名前空間レベルでのルックアップには使用できないため、 (特定のインスタンス化タイプの) ADLを介してのみ使用できるためT、コンパイラーは2つの値のスワップとの一致としてそれを検出できない可能性がありintます。

彼の答えの中で、Jesse Goodは、Matrixテンプレート内でフレンド関数を定義したので、MatrixとMatrixの各インスタンス化には、スワップ関数の同じ定義が含まれると述べています。

クラス内で定義されたフレンド関数は、名前空間レベルの関数を宣言および定義します。この場合も、宣言はクラス内でのみ使用可能であり、ADLを介してアクセスできます。これがテンプレート内で行われると、関数を使用するテンプレートのインスタンス化ごとに、名前空間レベルでテンプレート化されていない無料の関数が定義されます。つまり、異なる定義が生成されます。クラステンプレートスコープ内では、テンプレートの名前はインスタンス化されている特殊化を識別します。つまり、内部Matrix<T>では、識別子Matrixはテンプレートの名前ではなく、テンプレートの1つのインスタンス化を示します。

テスト2


namespace X {
   template <typename T>
   struct A {
      friend void function( A ) {}
   };
   template <typename T>
   void funcTion( A<T> ) {}
}
int main() {
   using namespace X;
   A<int> ai;    function(ai); funcTion(ai);
   A<double> ad; function(ad); funcTion(ad);
}

$ make test.cpp
$ nm test | grep func | c++filt
0000000100000e90 T void X::funcTion<double>(A<double>)
0000000100000e80 T void X::funcTion<int>(A<int>)
0000000100000e70 T X::function(A<double>)
0000000100000e60 T X::function(A<int>)

の出力はnmシンボルのリストでありc++filt、マングルされた名前をC++構文の同等のものに変換します。プログラムの出力は、 2つのタイプに対してインスタンス化されたテンプレートであり、X::funcTion 2X::functionつのオーバーロードされた非テンプレート関数であることを明確に示しています。繰り返しますが、 2つのテンプレート化されていない関数。


同じ関数を生成すると主張することはほとんど意味がありません。たとえばstd::cout << lhs、コードがoperator<<関数の現在のインスタンス化のためにの正しいオーバーロードを選択する必要があるなど、関数呼び出しがあったことを考慮してください。aとoroperator<<をとることができるシングルはありません(どのタイプでもテンプレートをインスタンス化することを妨げるものは何もありません。intunsigned longstd::vector<double>

ildjarnによる回答は代替案を提案していますが、動作の説明は提供していません。usingディレクティブはusing宣言とは完全に異なるため、代替手段は機能します。特に、前者(using namespace X;)はルックアップを変更して、名前空間Xの識別子が現在のコードの囲んでいる名前空間の1つで名前空間レベルで使用できるようにします(名前空間のツリーを構築する場合、宣言はブランチの場所で使用できます含むは、 using-directiveXが使用されるコードを含むブランチと一致します)。

一方、using-declarationusing std::swap;)は、 using-declarationが存在するコンテキストで関数の宣言を提供します。これが、スワップ関数を実装するためにusing-declarationsを使用し、 using-directivesを使用しない必要がある理由です。std::swap

テスト3


namespace Y { struct Z {}; void swap( Z&,Z& ); }
namespace X {
   struct A { int a; Y::Z b; };
   void swap( A& lhs, A& rhs ) {
      //using namespace std;       // [1]
      using std::swap;             // [2]
      swap( lhs.a, rhs.a );        // [3]
      swap( lhs.b, rhs.b );        // [4]
   }
}

using-directive [1]を使用した場合、::std名前空間のシンボルは、::X::swap(X::A&,X::A&)で宣言されているかのように関数内で実行されるルックアップに使用できます(これはと::の共通の祖先です)。これで、[3]のスワップは、ADLを介して関数を検出しないため、囲んでいる名前空間で関数の検索を開始します。最初に囲む名前空間はであり、関数が含まれているため、ルックアップが停止し、オーバーロードの解決が開始されますが、の有効なオーバーロードではないため、コンパイルに失敗します。::std::XswapswapXswapX::swapswap(int&,int&)

using-declarationを使用することにより、宣言を(関数内で!)std::swapのスコープに持ち込みます。X::swapこの場合も、[3]ではADLは適用されず、ルックアップが開始されます。現在のスコープ(関数内)では、の宣言が検出std::swapされ、テンプレートがインスタンス化されます。[4]では、ADLが起動し、関数および/またはでswap定義された関数を検索し、それを現在のスコープで見つかったオーバーロードのセットに追加します(ここでも、)。この時点で、はよりも適切に一致し(完全に一致する場合、テンプレート化されていない関数はテンプレート化された関数よりも適切に一致します)、期待される動作が得られます。::Y::Z::Y::std::swap::Z::swapstd::swap


于 2012-07-20T04:03:01.480 に答える
1

次の行をに含めますMatrix

template<typename U> friend void swap(Matrix<U> &first, Matrix<U> &second);

swapクラスの外側を定義します。エラーが発生する理由は、テンプレート内でフレンド関数を定義したため、およびfunction template has already been definedの各インスタンス化にスワップ関数の同じ定義が含まれるためです。Matrix<unsigned short>Matrix<char>Matrix

于 2012-07-19T23:33:36.890 に答える
1

以下は、VC ++2010SP1を使用して私にとってきれいにビルドされます。

Matrix.h:

#pragma once

#include <algorithm>
#include <vector>

namespace my_space
{
    template<typename T>
    class Matrix
    {
    public:
        Matrix(unsigned const w, unsigned const h)
          : width_(w), height_(h), data_(w * h)
        { }

    private:
        unsigned width_;
        unsigned height_;
        std::vector<T> data_;

        friend void swap(Matrix& lhs, Matrix& rhs)
        {
            using std::swap;
            swap(lhs.width_,  rhs.width_);
            swap(lhs.height_, rhs.height_);
            swap(lhs.data_,   rhs.data_);
        }
    };
}

.cpp:

#include "Matrix.h"

int main()
{
    using namespace my_space;
    using std::swap;

    int a(0), b(1);
    swap(a, b);

    Matrix<int> c(2, 3), d(4, 5);
    swap(c, d);

    Matrix<short> e(6, 7), f(8, 9);
    swap(e, f);
}

SSCCEヒントヒント)を投稿しなかったため、どこが間違っているのかを正確に把握することは非常に困難ですが、これを出発点として問題を絞り込むことができます。

于 2012-07-20T00:10:04.820 に答える