首页 > 解决方案 > 为什么 gcc 6.4.0 c++14 会自动将左值移动到右值

问题描述

我遇到了 gcc 编译器将局部变量(不是临时的)作为右值参数移动到函数的问题。我有一个简单的例子:

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

输出:

const A&
B is 1B
T&& (T is 1B)

我没想到它,因为移动赋值将右值归零。在我的情况下,它导致崩溃,因为在 b1=b2; 之后使用了 b2;

问题是为什么会发生。

标签: c++templatesc++14move-assignment-operatorlvalue-to-rvalue

解决方案


template<class T> B& operator=(T&&)
{ std::cout << "T&& (T is " << typeid(T).name() << ")\n"; return *this; }

不是移动赋值运算符,因为它是一个模板。来自 N4140,[class.copy]/19

用户声明的移动赋值运算符X::operator=是类的非静态非模板成员函数X只有一个类型为X&&const X&&volatile X&&或的参数const volatile X&&

您已经定义了一个接受转发引用的赋值运算符模板。在行

b1 = b2;

operator=(T&&)模板比复制赋值运算符 ( ) 更匹配,因为B& operator=(const B&)T被推断为B&并且不需要const限定转换。

如果你用 Boost.TypeIndex 替换对 的调用typeid,它会丢弃引用,这会变得很明显。

template<class T> B& operator=(T&&) 
{ 
  std::cout << "T&& (T is " << boost::typeindex::type_id_with_cvr<T>().pretty_name() << ")\n";
  return *this;
}

现场演示

输出变为

const A&
B is B
T&& (T is B&)

如果您不想operator=(T&&)被选中,则可以对其进行约束,以便在以下情况下将其从重载决议中删除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; 
}

(如果涉及继承,您可能希望使用is_convertible而不是)is_same

现场演示


推荐阅读