-Woverloaded-virtual with default shallow copy operator

125 Views Asked by At

This warning is issued by the G++ compiler starting from GCC-13.

  • Clang++ 17 is happy with this code.

  • MSCV 19 produces the similar warning

(39): warning C4263: 'Derived &Derived::operator =(const Derived &)': member function does not override any base class virtual member function
(46): warning C4264: 'const Parent &Parent::operator =(int &)': no override available for virtual member function from base 'Parent'; function is hidden
(10): note: see declaration of 'Parent::operator ='
(5): note: see declaration of 'Parent'

Hiding the operator does not help either.

To me it looks like a flaw in C++ standard.

Why do I need to implement assignment operator in derived class instead of inheriting from parent class?


Let me try to elaborate on this topic:

  • GCC 13.x and MSVC 19 generate the implicitly-defined copy operators even if they not supposed to do this.
  • The implicitly-defined copy operator of the derived class hides the user defined assignment operator of the base class.

Deleted copy assignment operator

An implicitly-declared or explicitly-defaulted (since C++11) copy assignment operator for class T is undefined (until C++11)defined as deleted (since C++11) if any of the following conditions is satisfied:

...

  • T has a non-static data member of a reference type.

...

I modified the code to avoid the generation of implicitly-defined copy assignment operator. But GCC and MSVC still want them.

Why the implicitly-defined copy assignment operators are generated in this case (by some compilers)?

Why some compilers claim that operator is hidden even it has different signature?

struct Parent
{
    Parent(int &x) : _x(x) {};
    virtual ~Parent() = default;

    virtual const Parent & operator=(int &x)
    {
        x = 0;
    return *this;
    }

    int &_x;
};

struct Derived : public Parent
{
    Derived(int &x) : Parent(x) {};
    virtual ~Derived() = default;
};

int main()
{
    int x = 0;
    Derived D1(x), D2(x);

#if (true)
    /*
    g++ 13.2: g++ --std=c++14 -o Woverloaded-virtual-test -Wall -Wextra Woverloaded-virtual-test.cpp
    https://gcc.godbolt.org/z/PGoof73Md
    __________________________________________________________________________________________________________
        source>:6:28: warning: 'virtual const Parent& Parent::operator=(int&)' was hidden [-Woverloaded-virtual=]
            6 |     virtual const Parent & operator=(int &x)
            |                            ^~~~~~~~
        <source>:15:8: note:   by 'Derived& Derived::operator=(const Derived&)'
        15 | struct Derived : public Parent
            |        ^~~~~~~
        Compiler returned: 0
    */

    /*
    mscv v19.37: /Wall /std:c++14
    https://gcc.godbolt.org/z/P3PrcTjxs
    __________________________________________________________________________________________________________
        <source>(13): warning C4626: 'Parent': assignment operator was implicitly defined as deleted
        <source>(19): warning C4626: 'Derived': assignment operator was implicitly defined as deleted
        <source>(19): warning C4263: 'Derived &Derived::operator =(const Derived &)': member function does not override any base class virtual member function
        <source>(19): warning C4264: 'const Parent &Parent::operator =(int &)': no override available for virtual member function from base 'Parent'; function is hidden
        <source>(6): note: see declaration of 'Parent::operator ='
        <source>(1): note: see declaration of 'Parent'
    */
#else
    /*
    g++ 13.2: g++ --std=c++14 -o Woverloaded-virtual-test -Wall -Wextra Woverloaded-virtual-test.cpp
    __________________________________________________________________________________________________________

        <source>:6:28: warning: 'virtual const Parent& Parent::operator=(int&)' was hidden [-Woverloaded-virtual=]
            6 |     virtual const Parent & operator=(int &x)
            |                            ^~~~~~~~
        <source>:15:8: note:   by 'Derived& Derived::operator=(const Derived&)'
        15 | struct Derived : public Parent
            |        ^~~~~~~
        <source>: In function 'int main()':
        <source>:27:10: error: use of deleted function 'Derived& Derived::operator=(const Derived&)'
        27 |     D1 = D2;
            |          ^~
        <source>:15:8: note: 'Derived& Derived::operator=(const Derived&)' is implicitly deleted because the default definition would be ill-formed:
        15 | struct Derived : public Parent
            |        ^~~~~~~
        <source>:15:8: error: use of deleted function 'Parent& Parent::operator=(const Parent&)'
        <source>:1:8: note: 'Parent& Parent::operator=(const Parent&)' is implicitly deleted because the default definition would be ill-formed:
            1 | struct Parent
            |        ^~~~~~
        <source>:1:8: error: non-static reference member 'int& Parent::_x', cannot use default assignment operator
        Compiler returned: 1
    */

    /*
    clang++ 17.0.1: clang++ --std=c++14 -o Woverloaded-virtual-test -Wall -Wextra Woverloaded-virtual-test.cpp
    https://gcc.godbolt.org/z/WzWfcqW3M
    __________________________________________________________________________________________________________

        <source>:111:8: error: object of type 'Derived' cannot be assigned because its copy assignment operator is implicitly deleted
        112 |     D1 = D2;
            |        ^
        <source>:15:18: note: copy assignment operator of 'Derived' is implicitly deleted because base class 'Parent' has a deleted copy assignment operator
        15 | struct Derived : public Parent
            |                  ^
        <source>:12:10: note: copy assignment operator of 'Parent' is implicitly deleted because field '_x' is of reference type 'int &'
        12 |     int &_x;
            |          ^
        1 error generated.
        Compiler returned: 1
    */

    /*
    msvc v19.37
    https://gcc.godbolt.org/z/r4nss37K1
    __________________________________________________________________________________________________________
        <source>(13): warning C4626: 'Parent': assignment operator was implicitly defined as deleted
        <source>(19): warning C4626: 'Derived': assignment operator was implicitly defined as deleted
        <source>(19): warning C4263: 'Derived &Derived::operator =(const Derived &)': member function does not override any base class virtual member function
        <source>(19): warning C4264: 'const Parent &Parent::operator =(int &)': no override available for virtual member function from base 'Parent'; function is hidden
        <source>(6): note: see declaration of 'Parent::operator ='
        <source>(1): note: see declaration of 'Parent'
        <source>(94): error C2280: 'Derived &Derived::operator =(const Derived &)': attempting to reference a deleted function
        <source>(19): note: compiler has generated 'Derived::operator =' here
        <source>(19): note: 'Derived &Derived::operator =(const Derived &)': function was implicitly deleted because a base class invokes a deleted or inaccessible function 'Parent &Parent::operator =(const Parent &)'
        <source>(13): note: 'Parent &Parent::operator =(const Parent &)': function was implicitly deleted because 'Parent' has a data member 'Parent::_x' of reference type
        <source>(12): note: see declaration of 'Parent::_x'
    */

    D1 = D2;
#endif

    return x;
}


Original code is left here just for reference purpose.

#include <iostream>

using namespace std;

struct Parent
{
    Parent() = default;
    virtual ~Parent() = default;

    virtual const Parent & operator=(int &x)
    {
        x = 0;
        return *this;
    }
};

struct Derived : public Parent
{
    Derived() = default;
    virtual ~Derived() = default;

#if (true)
    /* Set to false to get rid of the following warning

       g++ -o Woverloaded-virtual-test -Wall -Wextra Woverloaded-virtual-test.cpp
       or
       clang++ -o Woverloaded-virtual-test -Wall -Wextra Woverloaded-virtual-test.cpp

       main.cpp:18:28: warning: ‘virtual const Parent& Parent::operator=(int&)’ was hidden [-Woverloaded-virtual=]
          18 |     virtual const Parent & operator=(int &x)
             |                            ^~~~~~~~
       main.cpp:48:24: note:   by ‘constexpr Derived& Derived::operator=(const Derived&)’
          48 |     constexpr Derived& operator=(const Derived&) = delete;
             |                        ^~~~~~~~
     */
#else
    virtual const Parent & operator=(int &x) override
    {
        return Parent::operator=(x);
    }
#endif

    constexpr Derived& operator=(const Derived&) = delete;

    void method(int &x)
    {
        x *= 2;
        return;
    }
};

int main()
{
    cout<<"Hello World";

    Derived D;

    int x = 2;
    D.method(x);

    return x;
}
2

There are 2 best solutions below

5
Toby Speight On

We have a Derived& operator=(const Derived&) whether we define it or not, and that hides the base class operator= name.

Derived& operator=(const Derived&) = delete does not have the effect of making it as if the function never existed. The name exists, but no longer has a function definition.

This = delete declaration still has the effect of hiding the base-class function.

We can fix this error the same way we make any base-class names participate in overload resolution - we make it visible again with using in the subclass:

    constexpr Derived& operator=(const Derived&) = delete;

    using Parent::operator=;

(I'll ignore the issue of whether a virtual operator=() is a good idea or not...)

0
emerg.reanimator On

I believe, that I found the good answers to my questions above.

Why do I need to implement assignment operator in derived class instead of inheriting from parent class?

The rationale is the class function hiding and function declaration matching. More explanation can be found here: Reason for C++ member function hiding.

http://eel.is/c++draft/class.virtual ISO/IEC 14882 11.7.3 Virtual functions, Note 2

https://isocpp.org/files/papers/N4860.pdf ISO/IEC 14882 12.3 Declaration matching, Example 1

It seems to be standard conform except the compiler warnings. They are not part of ISO standard.

Why the implicitly-defined copy assignment operators are generated in this case (by some compilers)?

Why some compilers claim that operator is hidden even it has different signature?

It is rather implementation dependent. See this discussion: https://discourse.llvm.org/t/woverloaded-virtual-with-default-shallow-copy-operator/75592

GCC 13.x compiler generates the assignment operator even if it not supposed to do so. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109740 for more details.


I have to admit, that the function hiding is not really applicable in this case because the implicit type conversion does not pertain to the classes. The call of operator =(const &B) cannot be implicitly replaced by operator =(const &A) call.


Coming to the solution for this warning. There are number of approaches, that can be applied here. The extension of the function declaration scope by using Parent::operator= is one of them. However doing this one must be aware of possible unwanted behaviour change of derived class, that is basically the reason the reason for this warning.

I prefer to address this warning by eliminating the possibility of the behaviour change - meaning avoid function class hiding. So my solution is to rework my code, and replace operator= with some other class function(s).


Thanks to everyone who contributed to this topic!