Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the following code compile even though I have undefined member functions?

Tags:

c++

sfinae

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

like image 825
Samaursa Avatar asked Feb 27 '12 21:02

Samaursa


2 Answers

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;
}
like image 97
bames53 Avatar answered Nov 04 '22 07:11

bames53


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.

like image 32
Konrad Rudolph Avatar answered Nov 04 '22 07:11

Konrad Rudolph