2016-12-28 98 views
2
#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 
} 

問題:爲什麼[std :: is_move_assignable]的行爲不如預期?

如果b是真的,那麼v = *m4.begin();應該沒問題。

問:

爲什麼如預期那樣std::is_move_assignable沒有表現?

錯誤消息:(鏘3.8 +的Visual Studio 2015更新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 
+2

這可能是你的實現中有'std :: is_move_assignable_v'的錯誤http://melpon.org/wandbox/permlink/9DNHpi8cKFTF5uEH – Danh

+0

@Danh不得不說,在示例中存在未定義的行爲之前,隨機訪問操作符[]在multimap上。 – paweldac

+1

@paweldac它的運行時間未定義的行爲,他問爲什麼這個代碼不能編譯 – Danh

回答

0

解引用.begin()迭代空容器的是未定義的行爲。

+0

這不是問題。問題是編譯時錯誤,而不是運行時錯誤。我更新了代碼以避免分散注意力。 – xmllmx

+0

@xmllmx如果你有一個構建錯誤,那麼你應該*說,所以,不要問爲什麼它不「*行爲*如預期」。當然還包括問題主體中的完整和完整的錯誤輸出。 –

+0

@xmllmx unordered_multimap沒有隨機訪問運算符[]。請再次檢查您的代碼。 – paweldac

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=的有效簽名僅在其實例化格式良好以避免此問題時才被聲明,並且也如T.C.指出的std::pair will do so in the future

+0

我已經更新了代碼。改用'std :: move(* m4.begin())'。 – xmllmx

+4

@xmllmx請不要關注單個句子。它不會改變我的答案的其餘部分,它涵蓋'v = std :: move(* m4.begin());'已經。 – hvd

+0

這是[LWG問題2729](https://timsong-cpp.github.io/lwg-issues/2729)。另外,OP使用'is_move_assignable'錯誤:'decltype'返回一個引用類型。 –

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。但是,正如@hvd所指出的那樣,由於該函數在pair模板中主要聲明,類型特徵不瞭解如果它由編譯器生成爲實際函數,後者將遇到錯誤。

+0

不錯,我錯過了這個,直到T.C纔看到你的答案。指出了同樣的事情。這不像複製分配相同。這是可能的,但可能是一個非常糟糕的想法,只有'operator ='超載纔會'S',這會使'S'不可複製分配。 – hvd