Clang linker error when overloading abstract operator=
Asked Answered
R

1

6

The VisualStudio 2013 compiler handles the following code just fine, but clang 5.0 and 6.2 gives me a linker error:

#include <memory>

using namespace::std;

class IBase
{
public:
    virtual IBase& operator=(const IBase& other) = 0;
};

class Base : virtual public IBase
{
public:
    Base& operator=(const IBase& other) override
    {
        const Base& b = dynamic_cast<const Base&>(other);
        return *this = b;
    }

    virtual Base& operator=(const Base& other)
    {
        return *this;
    }
};

class IDerived : virtual public IBase
{
};

class Derived : public IDerived, public Base
{
public:
    using Base::operator=;

};

int main(int argc, const char * argv[]) {
    shared_ptr<Derived> d1 = make_shared<Derived>();
    shared_ptr<Derived> d2 = make_shared<Derived>();
    *d2 = *d1;
}

Here's the build log output:

Ld /Users/Jennifer/Library/Developer/Xcode/DerivedData/Operator-bjjgcoxcziyegjgmazknrandutqz/Build/Products/Debug/Oper normal x86_64
    cd /Users/Jennifer/Documents/Operator
    export MACOSX_DEPLOYMENT_TARGET=10.9
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -arch x86_64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk -L/Users/Jennifer/Library/Developer/Xcode/DerivedData/Operator-bjjgcoxcziyegjgmazknrandutqz/Build/Products/Debug -F/Users/Jennifer/Library/Developer/Xcode/DerivedData/Operator-bjjgcoxcziyegjgmazknrandutqz/Build/Products/Debug -filelist /Users/Jennifer/Library/Developer/Xcode/DerivedData/Operator-bjjgcoxcziyegjgmazknrandutqz/Build/Intermediates/Operator.build/Debug/Oper.build/Objects-normal/x86_64/Oper.LinkFileList -mmacosx-version-min=10.9 -stdlib=libc++ -Xlinker -dependency_info -Xlinker /Users/Jennifer/Library/Developer/Xcode/DerivedData/Operator-bjjgcoxcziyegjgmazknrandutqz/Build/Intermediates/Operator.build/Debug/Oper.build/Objects-normal/x86_64/Oper_dependency_info.dat -o /Users/Jennifer/Library/Developer/Xcode/DerivedData/Operator-bjjgcoxcziyegjgmazknrandutqz/Build/Products/Debug/Oper

Undefined symbols for architecture x86_64:
  "IBase::operator=(IBase const&)", referenced from:
      IDerived::operator=(IDerived const&) in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

IBase::operator=(IBase const&) is defined in Base which Derived inherits from, and Derived is using Base::operator= so it should be defined for Derived, not overridden by the default assignment operator.

One solution I found was to remove the IBase::operator= method, but this is not ideal since it is a method any inheriting class would need to implement.

Does anyone know what the difference is and how to fix it? I'd like to keep the IBase::operator= method if possible.

Reneerenegade answered 22/12, 2015 at 8:38 Comment(0)
S
5

The problem is that a using-declaration does not count as a user-declared assignment operator [namespace.udecl]:

4 - [...] If an assignment operator brought from a base class into a derived class scope has the signature of a copy/move assignment operator for the derived class (12.8), the using-declaration does not by itself suppress the implicit declaration of the derived class assignment operator [...]

(In any case, using Base::operator= gives you an assignment operator with parameter type Base const&, which is not one of the parameter types qualifying as a copy assignment operator [class.copy]/17 - T, T&, T const&, etc.)

Because Derived does not have a user-declared copy assignment operator, one is generated automatically, which ends up calling IDerived::operator= which calls IBase::operator=. Note that automatically-generated copy assignment operators call subobject copy assignment operators ignoring virtual overrides:

Each subobject is assigned in the manner appropriate to its type:

  • if the subobject is of class type, as if by a call to operator= with the subobject as the object expression and the corresponding subobject of x as a single function argument (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes); [...]

A fix would be to write:

    Base& operator=(Derived const& other) { return Base::operator=(other); }

Note that MSVC 2015 rejects your code but works with the above fix:

main.cpp(36): warning C4250: 'Derived': inherits 'Base::Base::operator =' via dominance
main.cpp(14): note: see declaration of 'Base::operator ='
main.obj : error LNK2019: unresolved external symbol "public: virtual class IBase & __thiscall IBase::operator=(class IBase const &)" (??4IBase@@UAEAAV0@ABV0@@Z) referenced in function "public: class IDerived & __thiscall IDerived::operator=(class IDerived const &)" (??4IDerived@@QAEAAV0@ABV0@@Z)
main.exe : fatal error LNK1120: 1 unresolved externals
Sprightly answered 22/12, 2015 at 9:21 Comment(1)
Regarding the first paragraph in the answer, it may be worth clarifying that even if the using declaration brought in a declaration with the right parameter type, it still wouldn't suppress the implicit declaration of the copy assignment operator.Trakas

© 2022 - 2024 — McMap. All rights reserved.