2016-12-28 10 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 + Clang 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 должен был сказать, прежде чем было неопределенное поведение в примере, сейчас существует более оператор произвольного доступа [] на мультимажете. – 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. указывает, и Ям Маркович ответил также, ваше использование 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=, но если либо T1 или T2 не может быть назначен, пытаясь фактически использовать это приведет к ошибке.

Шаблоны классов могут гарантировать, что действительная подпись 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 issue 2729] (https://timsong-cpp.github.io/lwg-issues/2729). Кроме того, OP использует 'is_move_assignable' неправильно:' 'decltype 'возвращает ссылочный тип. –

3

Во-первых, в качестве *begin()returns an lvalue reference, вы фактически передавая Lvalue ссылку на std::is_move_assignable, которая затем реализуется с помощью std::is_assignable<T&, T&&>. Теперь обратите внимание на ссылку пересылки, которая преобразуется в ссылку lvalue. Это означает, что, обратившись к 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. указал на то же самое. Тем не менее, это не совсем то же самое, что и для копирования. Возможно, но, возможно, очень плохая идея, чтобы иметь только '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' "" "" "копирование" – hvd