为什么[std :: is_move_assignable]的行为不符合预期?

时间:2016-12-28 11:46:50

标签: c++ c++11 typetraits

#include <unordered_map>
#include <type_traits>

int main()
{
        std::unordered_multimap<int, string> m{ { 1, "hello" } };
        auto b = std::is_move_assignable_v<decltype(*m.begin())>;
        // b is true

        auto v = *m4.begin(); // ok
        v = std::move(*m4.begin()); // compile-time error
}

问题:

  

如果b为真,则v = *m4.begin();应该没问题。

问题:

  

为什么std::is_move_assignable的行为不符合预期?

错误消息:( Clang 3.8 + Visual Studio 2015 Update 3)

error : cannot assign to non-static data member 'first' with
const-qualified type 'const int'
              first = _Right.first;
              ~~~~~ ^

main.cpp(119,5) :  note: in instantiation of member function
'std::pair<const int, std::basic_string<char, std::char_traits<char>,
std::allocator<char> > >::operator=' requested here
              v = *m4.begin(); // error

3 个答案:

答案 0 :(得分:5)

  

如果b为真,则v = *m4.begin();应该没问题。

v = *m4.begin();是复制分配,而不是移动分配。移动作业为v = std::move(*m4.begin());

但是作为T.C.指出并且Yam Marcovic也回答了,你使用std::is_move_assignable是错误的,因为decltype(*m.begin())是一个引用类型。您最终没有完全检查移动可分配性,并且您的支票 最终适用于v = *m4.begin();

对于移动分配,检查应为std::is_move_assignable_v<std::remove_reference_t<decltype(*m.begin())>>

无论如何,is_move_assignable并不会太难以检查类型是否可移动可分配,它只检查类型是否将自身报告为可分配移动。

鉴于

template <typename T>
struct S {
  S &operator=(S &&) { T() = "Hello"; return *this; }
};

std::is_move_assignable<S<int>>::value会报告true,因为签名S<int> S::operator=(S<int> &&);格式正确。试图实际调用它会触发错误,因为T()无法分配,即使可能,也无法分配字符串。

在您的情况下,类型为std::pair<const int, std::string>。请注意那里的const

std::pair<T1, T2>目前始终声明operator=,但如果无法分配T1T2,则尝试实际使用它会产生错误。

类模板可以确保operator=的有效签名只有在其实例化形式良好以避免此问题时才会被声明,并且正如TC std::pair will do so in the future所指出的那样。

答案 1 :(得分:3)

首先,作为*begin() returns an lvalue reference,您实际上是将左值引用传递给std::is_move_assignable,然后通过std::is_assignable<T&, T&&>实现。std::is_move_assignable<SomeType&>。现在,注意那里的转发引用,它转换为左值引用。这意味着通过询问std::is_assignable<T&, T&>您已经有效地询问#include <iostream> #include <type_traits> struct S { S& operator=(const S&) = default; S& operator=(S&&) = delete; }; int main() { std::cout << std::is_move_assignable<S>::value; // 0 std::cout << std::is_move_assignable<S&>::value; // 1 } - 即。复制分配。当然,这有点误导。

您可能需要测试此代码以查看我的观点:

*begin()

所有这一切,pair<const int, string>&在这种情况下返回const int,因此无论如何它都不会是任何类型的,因为你无法分配给pair。但是,正如@hvd指出的那样,由于函数在HTTP-EQUIV="Refresh"模板中以原理声明,因此类型特征不理解如果编译器将其生成为实际函数,则后者会遇到错误

答案 2 :(得分:0)

解除引用空容器的.begin()迭代器是未定义的行为。