Home > Net >  Deriving class with protected equality operator results in deleted default
Deriving class with protected equality operator results in deleted default

Time:07-21

Suppose we have the following implementation:

class A {
    protected:
    bool operator==(const A&) const = default;
};

class B : public A {
    public:
    bool operator==(const B& b) const {
        return A::operator==(b);
    };
};

int main() {
    B x, y;
    x == y;
}

This works in gcc 12.1 and clang 14.0. I also assume this to be the default behavior of B::operator==(const B& b) as the standard states:

A class can define operator== as defaulted, with a return value of bool. This will generate an equality comparison of each base class and member subobject, in their declaration order. Two objects are equal if the values of their base classes and members are equal. The test will short-circuit if an inequality is found in members or base classes earlier in declaration order.

So we can just replace the above statement with =default right? not really...

class A {
    protected:
    bool operator==(const A&) const = default;
};

class B : public A {
    public:
    bool operator==(const B& b) const = default;
};

int main() {
    B x, y;
    // Fails on both clang and gcc
    // error: use of deleted function 'constexpr bool B::operator==(const B&) const'
    //     x == y;
    x == y;
}

If an operator's default behavior is ill-formed, then the default behavior is to implicitly delete the function. So something about the default behavior here is ill-formed.

The only hypothesis I have is we're trying to invoke A::operator== within the context of A(maybe via std::static_cast<A>(*this) == b), which illegal because the function is protected not public. In my implementation above we're invoking A::operator== from the context of B which is legal. I don't see where in the standardese this specific behavior is specified though.

Addendum: Here's the full compiler dump of the =default implementation above:

<source>:8:10: note: 'constexpr bool B::operator==(const B&) const' is implicitly deleted because the default definition would be ill-formed:
    8 |     bool operator==(const B& b) const = default;
      |          ^~~~~~~~
<source>:8:10: error: 'bool A::operator==(const A&) const' is protected within this context
<source>:3:10: note: declared protected here
    3 |     bool operator==(const A&) const = default;

and I'm anticipating comments of form "why are you doing this in the first place?". The idea of removing the ability to perform == on the base class but allowing the derived classes to perform == if they so wish is a common one, but the argument for using the free function operator==(const B& lhs, const B& rhs) is valid. That's the current workaround.

CodePudding user response:

The defaulted implementation does not try to call A::operator==(b) for the base comparison.

It does something like static_cast<const A&>(*this) == static_cast<const A&>(b) instead and applies overload resolution to it as usual. This is described in [class.eq]/2 and [class.eq]/3. Also see [class.compare.default]/3 and [class.compare.default]/6 for relevant definitions.

The expression above will resolve to static_cast<const A&>(*this).operator==(static_cast<const A&>(b)). The context of the overload resolution is the class B however and therefore the special protected rule in [class.protected] applies, which forbids the type mismatch on the left hand of . and the class in which the access happens.

  • Related