Reputation: 301
I encountered a problem where gcc compiler moved local variable (not temporary) as rvalue argument to a function. I have a simple example:
class A
{
public:
A() {}
A& operator=(const A&) { std::cout << "const A&\n"; return *this; }
A& operator=(A&&) { std::cout << "A&&\n"; return *this; }
};
class B
{
public:
B() {}
B& operator=(const B&) { std::cout << "const B&\n"; return *this; }
B& operator=(B&&) { std::cout << "B&&\n"; return *this; }
template<class T> B& operator=(const T&) { std::cout << "const T& (T is " << typeid(T).name() << ")\n"; return *this; }
template<class T> B& operator=(T&&) { std::cout << "T&& (T is " << typeid(T).name() << ")\n"; return *this; }
};
int main(int argc, char **argv)
{
A a1;
A a2;
a1 = a2;
B b1;
B b2;
std::cout << "B is " << typeid(B).name() << "\n";
b1 = b2;
}
The output:
const A&
B is 1B
T&& (T is 1B)
I did not expect it because move assignment zeros the rvalue. In my case it caused to crash because b2 was used in after b1=b2;
The question is why it happened.
Upvotes: 3
Views: 100
Reputation: 109119
template<class T> B& operator=(T&&)
{ std::cout << "T&& (T is " << typeid(T).name() << ")\n"; return *this; }
is not a move assignment operator because it's a template. From N4140, [class.copy]/19
A user-declared move assignment operator
X::operator=
is a non-static non-template member function of classX
with exactly one parameter of typeX&&
,const X&&
,volatile X&&
, orconst volatile X&&
.
You've defined an assignment operator template that takes a forwarding reference. In the line
b1 = b2;
the operator=(T&&)
template is a better match than the copy assignment operator (B& operator=(const B&)
) because T
will be deduced as B&
and no const
qualification conversion is required.
If you replace the calls to typeid
, which discards references, with Boost.TypeIndex this becomes apparent.
template<class T> B& operator=(T&&)
{
std::cout << "T&& (T is " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << ")\n";
return *this;
}
The output changes to
const A&
B is B
T&& (T is B&)
If you don't want operator=(T&&)
to be selected, you can constrain it so it's dropped from overload resolution if T=B
template<class T, std::enable_if_t<not std::is_same<B, std::decay_t<T>>{}, int> = 0>
B& operator=(T&&)
{
std::cout << "T&& (T is " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << ")\n";
return *this;
}
(you may want to use is_convertible
instead of is_same
if inheritance is involved)
Upvotes: 5