Why does the following code compile even though I

2019-04-24 07:27发布

问题:

I was halfway through working on this piece of code and thought this is obviously not going to compile before hitting the build button. I was surprised that it not only compiled, but linked and worked as well.

If I were to guess I would say that SFINAE is responsible for it compiling... is it?

struct BaseClass
{
public:
  BaseClass() {}

  template<typename T>
  BaseClass(const T& a_other)
  {
    int i = 0; // for break point
  }

  template<typename T>
  BaseClass& operator= (const T& a_other)
  {
    int i = 0; // for break point
    return *this;
  }

private:

  BaseClass(const BaseClass& a_other); // Does not have a definition
  BaseClass& operator= (const BaseClass& a_other); // Does not have a definition

};

struct MyClass : public BaseClass
{
};

int main()
{
  MyClass i, j;
  i = j;

  return 0;
}

EDIT: I am using Visual-C++ 2008, maybe it is an odd quirk of VS

回答1:

The code is not legal.

i = j calls the implicitly defined copy assignment operator in MyClass. This function calls the copy assignment operator for each of its sub-objects, including direct base classes [class.copy 12.8 p28].

If you add code to the copy assignment operator for BaseClass you can see where VS is going wrong:

  template<typename T>
  BaseClass& operator= (const T& a_other)
  {
    std::cout << typeid(T).name() << '\n';
    int i = 0; // for break point
    return *this;
  }

For me this prints out "struct MyClass". VS is calling the BaseClass copy assignment operator by passing the parameter received in MyClass:operator= directly, rather than just the BaseClass sub object of j.

SFINAE doesn't come into play because the template functions aren't failing. VS is simply generating the implicit copy assignment operator incorrectly.

To sum up: VS is generating the implicit copy assignment operator as

MyClass &operator=(const MyClass& rhs) {
    static_cast<BaseClass&>(*this).operator=(rhs);
    return *this;
}

When it should be:

MyClass &operator=(const MyClass& rhs) {
    static_cast<BaseClass&>(*this).operator=(static_cast<const BaseClass&>(rhs));
    return *this;
}


回答2:

Shot in the dark: the compiler instantiates the base class’ operator = with T = MyClass. I do now know whether this is legal or even required but it makes a certain kind of sense: the auto-generated code for operator = essentially looks like this (well, pseudo-code):

MyClass& operator =(MyClass const& other) {
    BaseClass::operator =(other);
    return *this;
}

Now the compiler finds that BaseClass::operator =<MyClass>(MyClass const&) is the best match and instantiates it.



回答3:

Well, since it cannot call BaseClass::operator=(const BaseClass&) (from the default generated MyClass::operator= for what it's worth) because it's private, it just calls template<typename T> BaseClass::operator(const T &) with T=BaseClass. So there is no call to a non-defined function.

I guess the situation would be different if the others were public, in which case the compiler would prefer those over the template, but since he cannot see them when being private, the template versions match equally well.



回答4:

The compiler will auto generate a (default) copy constructor for MyClass as one is not defined. If you change i and j's type from MyClass to BaseClass you will see the error you expected, as the compiler then tries to bind the private, unimplemented assignment operator.


Going into this a little deeper using MSVC 2010 Ultimate SP1, we can see the exact reason:

MyClass::operator=:
00201230  push        ebp  
00201231  mov         ebp,esp  
00201233  push        ecx  
00201234  mov         dword ptr [ebp-4],ecx  
00201237  mov         eax,dword ptr [__that]  
0020123A  push        eax  
0020123B  mov         ecx,dword ptr [this]  
0020123E  call        BaseClass::operator=<MyClass> (202130h)  
00201243  mov         eax,dword ptr [this]  
00201246  mov         esp,ebp  
00201248  pop         ebp  
00201249  ret         4 

the assignment operator is called for MyClass by BaseClass::=<T> using MyClass as the type through MyClasses default copy operator, whether this is a bug or an MSVC-ism is up to MS and the C++ standard to decide.



回答5:

MyClass will use an implicitly-defined copy assignment operator, since there's no user-declared version. Ac cording to the standard, that implicit copy-assignment will perform the following (C++03 12.8/13 "Copying class objects"):

  • Each subobject is assigned in the manner appropriate to its type: — if the subobject is of class type, the copy assignment operator for the class is used (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes);

Note that in 12.8/9 the standard defines a user-declared copy assignment operator as " a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&" (emphasis mine).

So according to the standard, the template function

  template<typename T>
  BaseClass& operator= (const T& a_other);

Should not be called by the MyClass implicit copy assignment operator. MSVC is acting in a non-standard fashion here. GCC rightly diagnoses this:

C:\temp\test.cpp: In member function 'MyClass& MyClass::operator=(const MyClass&)':
C:\temp\test.cpp:29:14: error: 'BaseClass& BaseClass::operator=(const BaseClass&)' is private
C:\temp\test.cpp:33:8: error: within this context
C:\temp\test.cpp: In function 'int main()':
C:\temp\test.cpp:40:7: note: synthesized method 'MyClass& MyClass::operator=(const MyClass&)' first required here 


标签: c++ sfinae