0

私はマトリックステンプレートクラスを書いています。乗算演算子をオーバーロードするまでは、すべてうまくいきました。私のクラスは次のようになります。

template <typename TNum> class Matrix
{
private:
    // ...
    TNum* Data;
public:
    const TMatIdx NRows; // Type TMatIdx defined somewhere else.
    const TMatIdx NCols;
    const TMatIdx Size;

    // ...
    // Matrix * matrix
    template <typename T2>
    const Matrix<TNum> operator*(const Matrix<T2>& right) const;

    // Matrix * number
    template <typename T2>
    Matrix<TNum>& operator*=(const T2& scale);
};

// Matrix * number
template <typename TNum, typename T2>
Matrix<TNum> operator*(Matrix<TNum> lhs, const T2& rhs);
// Number * matrix
template <typename TNum, typename T2>
Matrix<TNum> operator*(const T2& lhs, Matrix<TNum> rhs);

*同じ演算子を使用して、行列と数値の間で可能なすべての乗算の組み合わせをカバーしたいと考えています。

次に、2 つの s を乗算する小さなテスト プログラムを作成しましたMatrix<double>。私の clang++ コンパイラはあいまいさについて文句を言います。

test.cpp:46: error: ambiguous overload for 'operator*' in 'M * N'
matrix.h:225: note: candidates are: const QCD::Matrix<TNum> QCD::Matrix<TNum>::operator*(const QCD::Matrix<T2>&) const [with T2 = double, TNum = double]
matrix.h:118: note:                 QCD::Matrix<TNum> QCD::operator*(const T2&, QCD::Matrix<TNum>) [with TNum = double, T2 = QCD::Matrix<double>]
matrix.h:109: note:                 QCD::Matrix<TNum> QCD::operator*(QCD::Matrix<TNum>, const T2&) [with TNum = double, T2 = QCD::Matrix<double>]
test.cpp:52: error: ambiguous overload for 'operator*' in 'M * N'
matrix.h:225: note: candidates are: const QCD::Matrix<TNum> QCD::Matrix<TNum>::operator*(const QCD::Matrix<T2>&) const [with T2 = double, TNum = double]
matrix.h:118: note:                 QCD::Matrix<TNum> QCD::operator*(const T2&, QCD::Matrix<TNum>) [with TNum = double, T2 = QCD::Matrix<double>]
matrix.h:109: note:                 QCD::Matrix<TNum> QCD::operator*(QCD::Matrix<TNum>, const T2&) [with TNum = double, T2 = QCD::Matrix<double>]

T2 のすべての可能な特殊化を明示的に書き留めることなく、このあいまいさを克服することは可能ですか?

参考までに、これが私の実装です。

template<typename TNum> template <typename T2>
Matrix<TNum>& Matrix<TNum> ::
operator*=(const T2& rhs)
{
    for(TMatIdx i = 0; i < Size; i++)
        Data[i] *= rhs;
    return *this;
}

template<typename TNum> template <typename T2>
const Matrix<TNum> Matrix<TNum> ::
operator*(const Matrix<T2>& right) const
{
    Matrix<TNum> c(NRows, right.NCols);
    TNum sum_elems;
    for(TMatIdx i = 0; i < NRows; i++)
    {
        for(TMatIdx j = 0; j < right.NCols; j++)
        {
            sum_elems = TNum(0);
            for(TMatIdx k = 0; k < right.NRows; k++)
            {
                sum_elems += at(i, k) * right.at(k, j);
            }

            c.at(i, j) = sum_elems;
        }
    }
    return c;
}


template <typename TNum, typename T2>
Matrix<TNum> operator*(Matrix<TNum> lhs, const T2& rhs)
{
    lhs *= rhs;
    return lhs;
}

template <typename TNum, typename T2>
Matrix<TNum> operator*(const T2& lhs, Matrix<TNum> rhs)
{
    rhs *= lhs;
    return rhs;
}
4

2 に答える 2

3

c++11 でのソリューションを説明し、次に c++98 での実装方法を説明します。

C++11 では、ヘッダー<type_traits>に型関数と型述語が含まれます。これにより、制約の適用がより便利になります。

std::is_same<T1, T2>::valueT1がと同じ型の場合は true、T2それ以外の場合は false です。

typename std::enable_if< bool, T >::typeTbool が true の場合は適切に定義された型であり、それ以外の場合は不適切に定義されています。

コンパイラがテンプレート関数とメソッドの候補を探すとき、特殊化の試行が失敗してもエラーはスローされません。それは単にその候補者を捨てるだけです。つまり、次のコードはあいまいさを取り除きます。

template <typename TNum, typename T2>
typename std::enable_if< (!std::is_same<Matrix<TNum>, T2>::value),
Matrix<TNum> >::type operator*(const T2& lhs, Matrix<TNum> rhs);

この決定を行う際には、宣言のみが考慮されます。上記のロジックは意味的には合理的ですが、読むのは目障りです。そのため、c++11 はテンプレート エイリアスと constexpr 関数をサポートしています。

template<bool B, typename T = void>
using Enable_if = typename std::enable_if<B, T>::type;

template<typename T1, typename T2>
constexpr bool Is_same(){
  return std::is_same<T1, T2>::value;
}

上記は次のようになります。

template <typename TNum, typename T2>
Enable_if<( !Is_same<Matrix<TNum>, T2>() ),
Matrix<TNum> > operator*(const T2& lhs, Matrix<TNum> rhs);

コンセプトは、これをより便利にするツールを提供します。

C++11 を持っていない場合は、目を楽しませることはできません。ただし、Boost は同じ機能を提供します。どちらも持っていないと仮定すると、それらを実装することはひどいことではありません。

コンパイル時の関数は複数の言語規則に依存しているため、理解が困難です。enable_if最初に検討します。私たちはtypename enable_if<true, T>::type明確に定義されたいと思っていますが、typename enable_if<false, T>::typeナンセンスであることはありません. 特殊化を使用します。

template<bool B, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> {};

notice false は、関連するケースのちょうど半分を処理します。上記を噛むことは価値があります。

を実装するis_sameには、コンパイル時に true または false の概念が必要です。これは static const 変数で保証できます。is_sameテンプレート引数が同じ場合は常に、コンパイル時に真の値が必要です。専門化システムのルールは、これを直接処理します。

template<typename, typename>
struct is_same{
  static const bool value = false;
};

template<typename T>
struct is_same<T, T>{
  static const bool value = true;
};

これはあなたが望むことをするはずです。さらに一歩抽象化して、構造体の別のペアを作成できることに注意してください。

struct false_type {
  static const bool value = false;
};

struct true_type {
  static const bool value = true;
};

その後、次のようにis_sameなります。

template<typename, typename>
struct is_same : false_type {};

template<typename T>
struct is_same<T, T> : true_type {};

これにより、関数のように見えます。

メタプログラムをヘッダー ファイルに分割する方が簡単なので、カテゴリ ソリューションよりもこれを好みます。その後、ロジックを別の場所で再利用できます。それでも、c++11 やブーストを使用していない場合、必要なコンパイル時関数を作成することは頭痛の種になる可能性があります。

複雑な(または単純な再設計)を使用することが現在および将来の要件を満たす場合は、それを優先してください。それ以外の場合、このソリューションは合理的に将来性があると思います。

于 2013-10-21T20:04:21.993 に答える