Sasha Itin
Sasha Itin

Reputation: 301

why gcc 6.4.0 c++14 moves automatically lvalue to rvalue

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

Answers (1)

Praetorian
Praetorian

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 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

Upvotes: 5

Related Questions