Неправильный оператор-член выводится по правильному глобальному.

У меня есть классы 3x3 Matrix и 3x1 Vector. У меня есть два оператора умножения; один для умножения матрицы на скаляр, другой для умножения матрицы на векторный объект. Оператор матрично-скалярного умножения является членом класса Matrix, а оператор матрично-векторного умножения является глобальным.

#include <initializer_list>
#include <array>

template <class T>
class Matrix
{
    public:
        Matrix(std::initializer_list<T> List);
        Matrix() : Matrix({0,0,0,0,0,0,0,0,0}) {}

        template <class S>                      // THE COMPILER TRIES TO USE
        Matrix<T> operator*(const S & Scalar);  // THIS OPERATOR IN BOTH CASES.

        const T & operator()(size_t i, size_t j) const;

    private:
        static constexpr size_t SIZE = 3;
        static constexpr size_t AREA = SIZE * SIZE;
        std::array<T, AREA> E;
};

template <class T>
Matrix<T>::Matrix(std::initializer_list<T> List)
{
    if (List.size() != AREA) throw("Error!");
    for (size_t i=0; i<AREA; i++)
    {
        E[i] = *(List.begin() + i);
    }
}

template <class T>
const T & Matrix<T>::operator()(size_t i, size_t j) const
{
    return E[SIZE * j + i];
}

template <class T>
template <class S>
Matrix<T> Matrix<T>::operator*(const S & Scalar)
{
    const T ScalarT = static_cast<T>(Scalar);
    Matrix<T> Result;
    for (size_t i=0; i<AREA; i++)
    {
        Result.E[i] = E[i] * ScalarT;
    }
    return Result;
}

template <class T>
class Vector
{
    public:
        Vector(std::initializer_list<T> List);
        Vector() : Vector({0,0,0}) {};
        const T & operator()(size_t i) const;
              T & operator()(size_t i);

    private:
        static constexpr size_t SIZE = 3;
        std::array<T, SIZE> E;
};

template <class T>
Vector<T>::Vector(std::initializer_list<T> List)
{
    if (List.size() != SIZE) throw("Error!");
    for (size_t i=0; i<SIZE; i++)
    {
        E[i] = *(List.begin() + i);
    }
}

template <class T>
const T & Vector<T>::operator()(size_t i) const
{
    return E[i];
}

template <class T>
T & Vector<T>::operator()(size_t i)
{
    return E[i];
}

template <class T>  // THE COMPILER NEVER TRIES USING THIS GLOBAL OPERATOR.
Vector<T> operator*(const Matrix<T> & Mat, const Vector<T> & Vec)
{
    Vector<T> Result;
    Result(0) = Mat(0,0) * Vec(0) + Mat(0,1) * Vec(1) + Mat(0,2) * Vec(2);
    Result(1) = Mat(1,0) * Vec(0) + Mat(1,1) * Vec(1) + Mat(1,2) * Vec(2);
    Result(2) = Mat(2,0) * Vec(0) + Mat(2,1) * Vec(1) + Mat(2,2) * Vec(2);
    return Result;
}

int wmain(int argc, wchar_t *argv[]/*, wchar_t *envp[]*/)
{
    Matrix<float> Mat1({2,  0,  0,
                        0,  2,  0,
                        0,  0,  2});

    Vector<float> Vec1({1,
                        2,
                        3});

    Matrix<float> Mat2 = Mat1 * 2;      // Matrix-Scalar Multiplication
    Vector<float> Vec2 = Mat1 * Vec1;   // Matrix-Vector Multiplication

    return 0;
}

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

Если я удалю оператор матрично-скалярного умножения и строку, где я его использую, программа запустится успешно. И наоборот, если я удалю оператор умножения матрицы на вектор, он снова запустится успешно. Они просто не ладят. И когда он запускается (в любом случае), он делает все расчеты правильно.

Что здесь происходит не так?

Компилятор и IDE: Microsoft Visual Studio 2015 Community Edition


person hkBattousai    schedule 08.03.2016    source источник
comment
Не могли бы вы привести минимальный пример? Здесь много ненужных деталей.   -  person TartanLlama    schedule 08.03.2016
comment
@TartanLlama Моя реальность состоит из ОГРОМНЫХ матричных и векторных классов. Я перенес сюда только необходимые функции, которые буду использовать в этом минимальном примере. Это уже очень упрощенная версия. Я не знаю, где еще я могу сократить.   -  person hkBattousai    schedule 08.03.2016
comment
@SimonKraemer А, извини, я исправил.   -  person hkBattousai    schedule 08.03.2016
comment
template<class S> ... (const S&) соответствует всем объектам . Это не означает только скалярные типы или что-то в этом роде.   -  person M.M    schedule 08.03.2016
comment
Разрешение перегрузки становится очень сложным, когда у вас есть версии одного и того же оператора, не являющиеся членами. Я настоятельно рекомендую использовать только тех, кто не является членом operator*. (Это, вероятно, не решит вашу проблему, но может устранить некоторые отвлекающие факторы или мешающие факторы)   -  person M.M    schedule 08.03.2016
comment
Сделайте это Matrix<T> operator*(const S & Scalar) const.   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@MM или измените порядок определений типов и создайте обе функции-члены. Или просто используйте T вместо другого параметра шаблона.   -  person Simon Kraemer    schedule 08.03.2016
comment
@hkBattousai Есть ли какая-то особая причина, по которой вы просто не принимаете T для своего оператора-члена?   -  person Simon Kraemer    schedule 08.03.2016
comment
Частичный порядок @M.M делает const Vector<T>& предпочтительным по сравнению с const S&, ничего не усложняется в операторах-членах и операторах, не являющихся членами, по крайней мере, здесь член operator*, вероятно, должен быть квалифицирован как const, хотя clang по-прежнему находит это неоднозначным   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@SimonKraemer Нет особой причины. Я просто хотел сделать его максимально универсальным.   -  person hkBattousai    schedule 08.03.2016
comment
@hkBattousai Измените его на принятие только T, чтобы решить вашу проблему. Он также должен быть менее подвержен ошибкам. Проходя например. указатель не имеет никакого смысла, но работает с вашим текущим подходом. Но также позаботьтесь о таких вещах, как const-correctity и других вещах, упомянутых в комментариях выше.   -  person Simon Kraemer    schedule 08.03.2016
comment
@hkBattousai Это то, что я бы назвал минимальным примером.   -  person TartanLlama    schedule 08.03.2016
comment
@PiotrSkotnicki выглядит для меня как лязг.   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@TartanLlama Вы пропустили return *this; в операторе участника ;-)   -  person Simon Kraemer    schedule 08.03.2016
comment
@н.м. ну я не совсем уверен. В глобальной версии T используется дважды, для Matrix и Vector, поэтому clang может подумать, что ни один из них не является более специализированным, чем другой, отсюда и двусмысленность. Matrix<float> более специализирован, чем Matrix<T>, точно так же, как const Vector<T>& более специализирован, чем const S&   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@hkBattousai самое простое и переносимое решение - сделать член operator* глобальным и позволить вывести T из Matrix<T> (как в версии Vector<T>)   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@Piotr, неясно, должно ли поплавок входить в изображение. Мы частично упорядочиваем Matrix<float>::operator* или Matrix<T>::operator*?   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@н.м. Я предполагаю, что когда кто-то использует Matrix<float>, компилятор получает две перегрузки: template<class S> operator*(const Matrix<float>&, const S&) и template<class T> operator*(const Matrix<T>&, const Vector<T>&), то есть компилятор видит float, так как Matrix<float> является неявным параметром объекта.   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@Piotr Очевидно, gcc и msvc думают иначе.   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@н.м. да знаю, и не могу точно сказать кто прав, хотя поддерживаю clang   -  person Piotr Skotnicki    schedule 08.03.2016
comment
@Piotr Теперь я нашел случай, когда три компилятора приняли три разных решения. pastebin.com/Bp8Us9EA   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@PiotrSkotnicki clang 3.3 раньше принимал эту программу, 3.5 - нет. Исправление ошибки или новая ошибка?   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016
comment
@н.м. clang принимает код, на который вы вставили ссылку (выбор оператора-члена, с которым я согласен), вы уверены, что он не работает с 3.5?   -  person Piotr Skotnicki    schedule 08.03.2016
comment
Да, clang принимает, gcc отклоняет, а msvc принимает и выбирает автономного оператора.   -  person n. 1.8e9-where's-my-share m.    schedule 08.03.2016


Ответы (1)


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

Итак, в вашем случае, поскольку скаляр является параметром шаблона, он будет соответствовать любому типу, так как левый операнд оператора равен Matrix<T>, скалярное умножение будет соответствовать любому правому операнду.

Ты мог:

  • Просто сделайте оба operator* в одной области, чтобы они оба учитывались при разрешении перегрузки.
  • Измените скаляр operator, чтобы взять, например, Scalar<T>, который является тонкой оболочкой скалярного типа, а не универсального типа шаблона.
person Mark B    schedule 15.03.2016