clang 18 and operator overloads from multiple bases

156 Views Asked by At

I have upgraded clang from ver 14 to the latest one (18 on trunk) and it seems the only problem I have in my code is operator inheritance from multiple base classes (an example and reference to godbolt below). This is a simplified example where I have a final vector class that derives a few mixins that add functionality. In this case, these mixins are vector-scalar multiplication and vector-vector multiplication, implemented as vector dot product. Both mixings add operator* but both of these operators are specialized to work with specific kinds of types so there should not be any ambiguity. And that worked well until now. So my question is - is this new error is implementation of some standard requirement or this is just a bug in the compiler beta version and my code is okay?

https://godbolt.org/z/ca8Yefjdc

#include <cstddef>
#include <type_traits>
#include <array>

template<
    typename T, size_t N,
    template <typename, size_t> typename FinalTemplate
>
struct VectorData
{
    std::array<T, N> data;
};

template<
    typename T, size_t N,
    template <typename, size_t> typename FinalTemplate
>
struct VectorScalarMultiplication
{
    using Final = FinalTemplate<T, N>;

    template<typename U, typename Enable = std::enable_if_t<(
        std::is_integral_v<U> || std::is_floating_point_v<U>
    )>>
    Final operator*(const U value) const
    {
        auto copy = static_cast<const Final&>(*this);
        for (size_t i = 0; i != N; ++i)
        {
            copy.data[i] *= value;
        }

        return copy;
    }
};



template<
    typename T, size_t N,
    template <typename, size_t> typename FinalTemplate
>
struct VectorVectorMultiplication
{
    using Final = FinalTemplate<T, N>;

    T operator*(const Final& b) const
    {
        auto& a = static_cast<const Final&>(*this);
        T r{};
        for (size_t i = 0; i != N; ++i)
        {
            r += a.data[i] * b.data[i];
        }

        return r;
    }
};

template<typename T, size_t N>
struct Vector
    : public VectorData<T, N, Vector>
    , public VectorScalarMultiplication<T, N, Vector>
    , public VectorVectorMultiplication<T, N, Vector>
{
};

int main()
{
    Vector<float, 3> a;
    return static_cast<int>(a * a);
}

If that helps, adding these lines into the final class solves the issue but kind of breaks the whole idea of mixins:

    using VectorScalarMultiplication<T, N, Vector>::operator*;
    using VectorVectorMultiplication<T, N, Vector>::operator*;
2

There are 2 best solutions below

0
cigien On BEST ANSWER

This is a long standing bug in Clang where ambiguous members in base classes were not diagnosed. The example in the bug report linked above uses operator(), but the bug occurred for other operator overloads as well (although regular named functions were diagnosed correctly). The bug was recently fixed.

So your code was never supposed to work the way it was written, and now it doesn't work since the Clang bug has been fixed.

0
Brian Bi On

When the compiler interprets the expression a * a it does so by looking for both member and non-member candidates.

In the member part of the lookup, the compiler performs a lookup for operator* in the scope of the left operand's class, that is, in the scope of class Vector.

When a member name lookup finds different entities inherited from different base classes, the program is ill-formed. This has always been the case, but it appears that the older version of Clang did not diagnose it. Your code should not have been accepted then, but was anyway.

Evidently, the mixin pattern doesn't yield ideal results when there are multiple mixins that contribute members with the same name. In this particular case a better result is achieved if each operator* is defined as a non-member hidden friend instead of a member. In that case the member name lookup rules don't apply, and argument-dependent lookup is used instead. No using-declarations required.