4

GCCで正しく実行しているときに、Apple LLVMコンパイラ(XCode 4.5.2に付属)を使用すると問題が発生しました。コンパイラの問題に関する議論よりも重要なことですが(GCCは正しいと思います)、これにより、オーバーロードされた演算子が関係する場合のテンプレートの特殊化の解決順序について疑問が生じます[1]。

template <class T> class matrix_t乗算の結果タイプ(スカラー、行列、またはベクトルを使用)を定義する特性を持つ単純な行列クラスについて考えてみます。これは、次のコードのようになります。

template <class T, class U, class Enable = void>
struct matrix_multiplication_traits {
  //typedef typename boost::numeric::conversion_traits<T,U>::supertype type;
  typedef double type;
};

template <class T, int N>
struct vectorN {
  std::vector<T> vect;
  vectorN() : vect(N) { for(int i = 0; i < N; i++) vect[i] = i; }
};


template <class T>
class matrix_t {
  std::vector<T> vec;
public:
  matrix_t<T> operator*(matrix_t<T> const& r) const {
    std::cout << "this_type operator*(this_type const& r) const" << std::endl;
    return r;
  }

  template <class U>
  matrix_t<typename matrix_multiplication_traits<T, U>::type>
  operator*(U const &u) const {
    std::cout << "different_type operator*(U const &u) const" << std::endl;
    return matrix_t<typename matrix_multiplication_traits<T, U>::type>();
  }

};

の専門化も検討operator*してくださいvectorN

template <class T, class U, int N>
//vectorN<typename matrix_multiplication_traits<T,U>::type, N>
vectorN<double, N>
operator*(matrix_t<T> const&m, vectorN<U, N> const&v)
{
  std::cout << "vectorN operator*(matrix, vectorN)" << std::endl;
  //return vectorN<typename matrix_multiplication_traits<T,U>::type, N>();
  return vectorN<double, N>();
}

簡単なテストプログラムを考えてみましょう。

int main(int argc, const char * argv[])
{
  matrix_t<double> test;
  vectorN<double, 10> my_vector;
  test * my_vector; // problematic line
  return 0;
}

「問題のある行」は、operator*(matrix_t<T> const&, vectorN<U, N> const&)GCCとtemplate <class U> matrix_t<T>::operator*(U const&) constLLVMでグローバルに定義されたものを実行します。つまり、matrix_t<T>::operator*(U const&)はすべてのテンプレート特殊化ルックアップをキャッチしているようなものです。簡単な「修正」は、グローバルoperator*をマトリックスクラスに移動することです。

私は最初、それが特性クラスの問題であり、複雑すぎるか誤っている可能性があると考えました(SFINAE)。ただし、特性を単純化するか、(貼り付けコードのように)完全に無効にしても、エラーが発生します。次に、それは順序の問題だと思いました(Herb Shutterの記事のように)が、宣言と定義operator*の間でグローバルを移動matrix_tしても状況は変わりません。

ここに質問があります

もちろん、本当の問題はそれtemplate <class U> matrix_t::operator*(U const&) constがあまりにも一般的であるということです、しかし:

  • この種の問題は標準でカバーされているものですか?
  • クラス内で定義された演算子のオーバーロードは、グローバルに定義された演算子のオーバーロードよりも優先されますか?
  • (語彙の問題のように)資格はoperator*(matrix_t<T> const&, vectorN<U, N> const&)何ですか?テンプレートオーバーロード演算子の特殊化?これはテンプレートの特殊化ですか、それともオーバーロードされた関数ですか?そのための基本的な定義は何ですか?それは本質的にオーバーロードされた演算子なので、私は少し迷っています。

[1]テンプレートの特殊化順序に関するハーブシャッターのアドバイスを読みました。

4

1 に答える 1

2

クラス内で定義された演算子のオーバーロードは、グローバルに定義された演算子のオーバーロードよりも優先されますか?

いいえ。C++11規格のパラグラフ13.3.1/2によると:

候補関数のセットには、同じ引数リストに対して解決されるメンバー関数と非メンバー関数の両方を含めることができます。引数とパラメーターのリストがこの異種セット内で比較できるように、メンバー関数は、メンバー関数が呼び出されたオブジェクトを表す暗黙的なオブジェクトパラメーターと呼ばれる追加のパラメーターを持っていると見なされます。過負荷を解決するために、静的メンバー関数と非静的メンバー関数の両方に暗黙のオブジェクトパラメーターがありますが、コンストラクターにはありません。

さらに、標準のメンバー関数のどこにも、非メンバー関数よりも優先される、またはその逆が言及されていません。

この種の問題は標準でカバーされているものですか?

はい。グローバル演算子が選択される理由(すべきです!)は、クラス内で定義された関数テンプレートよりも特殊なvectorN<U, N> const&テンプレートであるためです。テンプレート引数の推定後、両方とmatrix_t<T> const&を一致させることができますmatrix_t<T> const& rが、前者はより特殊です。後者、したがって、それが好ましい。

13.3.3 / 1項による:

これらの定義を前提として、すべての引数iについて、ICSi(F1)がICSi(F2)よりも悪い変換シーケンスではない場合、実行可能な関数F1は別の実行可能な関数F2よりも優れた関数であると定義されます。

[...]

F1とF2は関数テンプレートの特殊化であり、F1の関数テンプレートは、14.5.6.2で説明されている半順序規則に従って、F2のテンプレートよりも特殊化されています。

したがって:

つまり、matrix_t :: operator *(U const&)がすべてのテンプレート特殊化ルックアップをキャッチしているようなものです。

この動作はバグと見なされます。ただし、これらは特殊化ではなく、オーバーロードであることに注意してください。

ついに:

の資格はoperator * (matrix_t<T> const&, vectorN<U, N> const&)何ですか?

(グローバル)演算子オーバーロードテンプレートと言えるでしょう。テンプレート関数の特殊化は構文が異なるため、特殊化ではありません。したがって、それが専門としている主要なテンプレートはありません。これは、インスタンス化されると、乗算演算子のオーバーロードを生成する単なる関数テンプレートです。

于 2013-02-22T16:53:02.273 に答える