3

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.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Sasha Itin
  • 161
  • 1
  • 6
  • I don't see any "zeros the rvalue" in the code you show. Please try to create a [mcve] to show us. – Some programmer dude Feb 11 '19 at 14:42
  • One line after `b1=b2` `b2` will be deleted, so why not optimize the code and use a move instead of a copy? Looks like a clever optimization to me. – Werner Henze Feb 11 '19 at 14:45
  • Oh and if you need help with a crash in your code, then please ask about it directly instead. Also please read about [how to ask good questions](http://stackoverflow.com/help/how-to-ask), as well as [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). – Some programmer dude Feb 11 '19 at 14:52
  • 1
    read about reference collapsing and perfect forwarding – Guillaume Racicot Feb 11 '19 at 14:59

1 Answers1

5
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 class X with exactly one parameter of type X&&, const X&&, volatile X&&, or const 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;
}

Live demo

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)

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328