Search code examples
c++inheritancecopy-constructorassignment-operatorusing-declaration

Assignment operator overloads have similar conversions (only in VS)


I have a class hierarchy with three classes (A, B and C). A and B are base-classes, parametrized with the derived Type. Class C is derived from both, A and B.

The class B provides an assignment operator for objects of type A and class C inherits this assignment operator with the using super::operator= declaration.

When I define a constructor in class B from objects of type A, I get the Error: two overloads have similar conversions (C2666) in Visual Studio 2013, but I don't get any error, or warning in gcc (4.8.2), clang (3.4) and intel icc (Studio 2015). (compiled with -Wall -pedantic)

Here the reduced example:

template <class Model> struct A {};

template <class Model> struct B
{
    B() {}; // default constructor

    // copy constructor for objects of type A
    template <class M> 
    B(A<M> const&) {} 

    // assignment operator for objects of type A
    template <class M>
    Model& operator=(A<M> const& rhs)
    {
        return static_cast<Model&>(*this);
    }
};

struct C : public B<C>, public A<C>
{
    typedef B<C>  super;

    // copy assignment operator
    C& operator=(C const& rhs) { return *this; }

    // adopt assignment operator for A<C> from super-class
    using super::operator=;
};

int main()
{
    C c;
    A<C> a;
    c = a;
}

If I would replace the templated class A by a non-templated class it also compiles in Visual Studio without errors - but this is not the way it could be solved.

My question is: is this construct well-formed in the sense that it is standard conform, or is the error-message correct? Does a specifier like explicit for the copy constructor in B helps to solve the problem?

By the way: In Visual Studio, I get the Warning: multiple assignment operators specified (C4522), because of the copy assignment operator in class C. Can somebody exmplain to me, why this should be a problem?


Solution

  • GCC and CLANG are correct and MSVC is wrong:

    What's the expected behaviour:

    The statement c=a; uses the operator= that you've defined in B, because an A<C> is not necessarily a C. So let's write down the the declaration of operator= of B<C> by manually doing the type substitution:

    template <class M> 
    C& operator=(A<M> const& rhs)
    

    As a is a A<C>, the obvious implicit instantiation candidate of this template would be:

     C& operator=(A<C> const& rhs)
    

    This is in fact the only possible instantiation (you could verify that GCC uses it by displaying the typeinfo).

    What is MSVC trying to do ?

    If you'd change simplify the class C to an even more minimalistic form, you'd still get theerror:

    struct C : public B<C>   // single inheritance
    {   using B<C>::operator=; };  // nothing else
    

    In fact the problem is caused by the constructor B(A<M> const&) :

    • comment it out, and the code will compile
    • make it explicit and the code will also compmile

    MSVC identifies wrongly a second potental candidate for the member function's implicit specialisation. As this constructor allows to convert implicitely from A<M> to B<C>, the candidate is:

     C& operator=(B<C> const& rhs)
    

    But according to the C++ standard, this shouldn't be envisaged by the compiler at all:

    14.8.1/6: Implicit conversions will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.

    So this is clearly a bug of MSVC.

    By the way:

    The warning about multiple assignemnt operators is just an information. Apparently MS assumes that this might be a frequent cause for mistakes. And now to the core question...