Question:
Hello everyone. There is Visual C ++ 2015 and the code:
#define SHOW_ME printf("%s\n", __FUNCSIG__)
template <class T>
struct wrapper {
int data = 10;
wrapper() = default;
template <class U> wrapper(const wrapper<U>&) { SHOW_ME; }
};
I am trying to provoke it like this:
wrapper<long> wp1;
wrapper<long> wp2{ wp1 };
Doesn't work as expected. For some reason, the default copy constructor is called instead of mine. Adding:
wrapper(const wrapper<T>&) = delete;
does not save the day. Compilation error, the compiler does not see the template constructor at close range and asks for something more appropriate. If we remove the const qualifier, then everything works:
template <class U> wrapper(wrapper<U>&) { SHOW_ME; }
by typing, I identified the following priority of overloads
(wrapper<T>&) > (wrapper<U>&) > (const wrapper<T>&) > default_ctor > (const wrapper<U>&)
I have given up on const for now, but the problems did not end there. If we try to apply this scheme for operator =, we get the following:
template <class U>
wrapper<T>& operator=(wrapper<U>&)
{ SHOW_ME return *this; }
...
wrapper<long> wp1;
wrapper<long> wp2;
wp1 = wp2;
The default version is called again, although const is no longer there. Going through different options, I accidentally discovered that if you add a definition to the class
wrapper<T>& operator=(const wrapper<T>&);
Then everything works and the option with class U is called, but how ?! I just added a definition that is not used in the code. Have I somehow overwritten the default operator? But even if I add a body to this function, the compiler will still prefer the template one, and without this definition it refused to see it.
- Is there some clear explanation of the entire logic of these overloads, or are these compiler bugs or am I doing something wrong?
- What is the most efficient way to secure classes against unauthorized copying?
Answer:
Meyers in Effective and Modern C ++ has a very similar situation (section 5.4). In short, the point is that normal congestion resolution begins.
The presence of = delete
does not mean that the constructor is not involved in overload resolution, but simply that it has no body. Further, when passing the lvalue wp1
to the constructor, you can pass it either to the generated constructor or to your templated one. The generated (or rather, the default constructor, it just is not generated – you have forbidden) the constructor turns out to be a more exact match, despite the fact that both for it and for the template one requires the addition of const
– the result is clear. But if we remove const
from the template, then it turns out to be more appropriate (does not require adding const
) and, accordingly, it is he who is called.
Does this clarify the situation?
If, for example, introduce (and disallow) copying by default without const
,
wrapper(wrapper<T>&) = delete;
and passing an rvalue at the same time – there will be no problem:
wrapper<long> wp2{std::move(wp1)};