Матричный шаблон C ++, неоднозначность между умножением матрицы на матрицу и числом матриц

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);

Im надеясь покрыть все возможные комбинации умножения среди матриц и чисел с одним и тем же * оператором.

Затем я написал небольшую тестовую программу, которая умножает два Matrix<double> s, мой компилятор 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?

И FYI, это моя реализация:

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;
}
Теги:
templates
matrix
operator-overloading
ambiguity

2 ответа

3
Лучший ответ

Я собираюсь описать решение с помощью С++ 11, а затем объясню, как реализовать его в С++ 98.

В С++ 11 заголовок <type_traits> включает функции типа и предикаты типа. Это делает принудительные ограничения более удобными.

std::is_same<T1, T2>::value true, если T1 - тот же тип, что и T2 и false в противном случае.

typename std::enable_if< bool, T >::type - это четко определенный тип T если bool истинно и не определено иначе.

Когда компилятор ищет функции и методы кандидата-кандидата, он не выдает ошибку, если попытка попытки специализации терпит неудачу. Он просто выбрасывает этого кандидата. Это означает, что следующий код устранит двусмысленность:

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);

При принятии этого решения учитываются только декларации. Вышеприведенная логика семантически разумна, но наглазник, чтобы читать. Таким образом, С++ 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);

концепции предоставят инструменты, которые сделают это более удобным.

Теперь, если у вас нет С++ 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> {};

обратите внимание, что ложные ручки обрабатываются ровно половиной соответствующих случаев. Стоит же разглядеть вышеизложенное.

Чтобы реализовать is_same, нам нужно понятие true или false во время компиляции. Мы можем гарантировать это с помощью статических константных переменных. Мы хотим, чтобы 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 {};

что делает его больше похожим на функцию.

Я предпочитаю это для решения категории, потому что проще отделить метапрограмму в файл заголовка. Затем вы можете повторно использовать логику в другом месте. Тем не менее, если вы не используете С++ 11 или даже повышаете, создание необходимых функций времени компиляции может быть головной болью.

Если использование сложной (или любой простой редизайна) удовлетворяет вашим текущим и будущим требованиям, предпочитайте это. В противном случае я думаю, что это решение является разумным будущим доказательством.

  • 0
    Хороший ответ и объяснение! Я попробовал это, и это работало безупречно (по крайней мере для моей крошечной тестовой программы :). Большое спасибо!
0

Это может быть помощь.

#include<utility>

template<class Type>
struct test{
private:
    struct _matrix{};
    struct _scalar{};
    template<class T>
    struct category{
        typedef _scalar type;
    };
    template<class T>
    struct category<test<T>>{
        typedef _matrix type;
    };

    template<class T>
    void do_foo(T, _matrix){}

    template<class T>
    void do_foo(T, _scalar){}

public:
    //c++11
    template<class T>
    void operator*(T&& a){
        do_foo(std::forward<T>(a), category<T>::type());
    }
    //Older Compiler
    //template<class T>
    //void operator*(const T& a){
    //  do_foo(a, category<T>::type());
    //}

};

int main(){
    typedef test<int> int_matrix;
    int_matrix obj;
    obj*int_matrix();
    obj*obj;
    obj*1;
    obj*1.;

    return 0;
}
  • 0
    Не могли бы вы дать несколько советов о том, как работает этот код? Также у меня может не быть доступа к компилятору c ++ 11. Мне нужно будет запустить этот код на некоторых суперкомпьютерах со странными старыми компиляторами ...
  • 0
    Используются следующие функции: перегрузка функций (do_foo) и специализация шаблонов классов. Std :: forward не является обязательным. Таким образом, трюк будет эффективен на старых компиляторах. Причина, по которой исходный код не работает, заключается в том, что для Matrix <T> нет предпочтительного соответствия. Действительно, я думаю, что в вашем случае невозможно получить предпочтительное совпадение с двумя шаблонами функций, и единственное решение, которое я могу придумать, это перегрузка функций. PS: я думаю, что вы можете просто определить оператор для матрицы классов <T> и T. Нет необходимости во всех типах из-за автоматического преобразования типов.
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню