Перегрузка оператора через наследование вызывает неоднозначность - PullRequest
1 голос
/ 06 ноября 2019

Я пишу некоторую библиотеку шаблонов (скажем, библиотеку линейной алгебры) и получил следующую сложную ошибку, которую я могу воспроизвести только на GCC (Clang и VC ++ работают как положено).

Библиотекасостоит из общих типов шаблонов, таких как

template<class C, int N>
class Vector;
template<class C, int N, int M = N>
class Matrix;

с некоторыми реализациями по умолчанию, которые не используются. Также есть набор классов интерфейса, подобных этим

template<class C, int N>
struct VecMulInterface
{
    Vector<C, N> operator*(const Vector<C, N>& v)
    {
        return static_cast<Matrix<C, N, N>*>(this)->mul_impl(v);
    }
};

template<class C, int N>
struct ScalMulInterface
{
    Matrix<C, N, N> operator*(const C& c)
    {
        return static_cast<Matrix<C, N, N>*>(this)->mul_impl(c);
    }
};

и соответствующим поставщикам реализаций

template<class C, int N>
struct MulImpl
{
public:
    Vector<C, N> mul_impl(const Vector<C, N>& v) const
    {        
        return {}; // imagine some logic here
    }
    Matrix<C, N, N> mul_impl(const C& c) const
    {     
        return {}; // imagine some logic here
    }
};

Наконец, у меня есть специализации шаблонов, которые используют все, что указано выше:

template<class C>
class Vector<C, 2>
{
public:
    C items[2];
    // ...
};

template<class C>
class Matrix<C, 2, 2>:
    public VecMulInterface<C, 2>,
    public ScalMulInterface<C, 2>,
    public MulImpl<C, 2>
{
public:
    C items[4];
    // ...
};

Я пытаюсь использовать их следующим образом:

Matrix<int, 2, 2> m;
Vector<int, 2> v1, v2;
v2 = m * v1; // <- error

Теперь GCC выдает ошибку «ошибка: запрос на член 'operator *' является неоднозначным». Но! Если я изменяю строку с ошибкой так, что я использую перегруженную функцию вместо 'operator *'

v2 = m.mul_impl( v1 ); // <- NO error

ИЛИ, если вместо наследования от соответствующих интерфейсов я помещаю операторы вне класса, как это:

template<class C, int N>
Vector<C, N> operator*(const Matrix<C, N, N>& m, const Vector<C, N>& v)
{
    return m.mul_impl(v);
}
template<class C, int N>
Matrix<C, N, N> operator*(const Matrix<C, N, N>& m, const C& c)
{
    return m.mul_impl(c);
}

все работает просто отлично. (VC ++ и Clang, кажется, работают правильно во всех случаях)

Может ли кто-нибудь объяснить причину такого поведения? Это ошибка компилятора, или я перешел к «неопределенному поведению» где-то в коде?

1 Ответ

2 голосов
/ 06 ноября 2019

Функции в разных областях не перегружаются. Для случая члена operator* две функции находятся в VecMulInterface и ScalMulInterface соответственно, поэтому они находятся в разных областях. В двух других случаях две функции находятся в одной и той же области.

Вы можете использовать декларацию using для преобразования их в одну и ту же область:

template<class C>
class Matrix<C, 2, 2> :
    public VecMulInterface<C, 2>,
    public ScalMulInterface<C, 2>,
    public MulImpl<C, 2>
{
public:
    using VecMulInterface<C, 2>::operator*;
    using ScalMulInterface<C, 2>::operator*;
    // ...
};

Более элегантный подход заключается виспользуйте friend функции в интерфейсах:

template <class C, int N>
struct VecMulInterface
{
    friend Vector<C, N> operator*(const Matrix<C, N, N>& m, const Vector<C, N>& v)
    {
        return m.mul_impl(m, v);
    }
};
...