c++ c++14 language-lawyer move-semantics libstdc++

`pair:: operator=(pair &&)` error con `auto &` operaciones de movimiento deducidas-¿regresión de libstdc++?



c++14 language-lawyer (1)

Esto es casi ciertamente un problema de punto de instanciación. Si haces algo que activa la creación de instancias de la definición de pair<int, Val> en main , entonces obtienes el error. De lo contrario, solo se crea una instancia cuando se crea una instancia de vector::emplace , que las implementaciones en cuestión aquí difieren hasta el final de la unidad de traducción (que está permitida, consulte [temp.point]/8 ), en cuyo punto el operador de asignación está completamente Definido y llamable.

a(v) activa la creación de instancias de la definición de pair<int, Val> porque es necesaria para ADL. Si escribe ::a(v) o (a)(v) (ambos suprimen ADL), el error desaparecerá. (Obviamente, a.back().first requiere la creación de instancias del pair<int, Val> : está accediendo a su miembro de datos).

Dado este programa:

struct Val { Val() = default; Val(Val&&) = default; auto& operator=(Val&&); }; /* PLACEHOLDER */ auto& Val::operator=(Val&&) { return *this; }

Sustituyendo /* PLACEHOLDER */ con ...

int main() { std::vector<std::pair<int, Val>> v; v.emplace(std::begin(v), 0, Val{}); }

... compila exitosamente en:

  • g ++ 6.2.0
  • g ++ 6.3.0
  • g ++ 7.0.1 (troncal)

  • clang ++ 3.9.1

  • clang ++ 5.0.0 (HEAD)

en wandbox

Sustituyendo /* PLACEHOLDER */ con ...

template <typename TVec> void a(TVec& v) { v.emplace(std::begin(v), 0, Val{}); } int main() { std::vector<std::pair<int, Val>> v; a(v); }

... compila exitosamente en:

  • g ++ 6.2.0
  • clang ++ 3.9.1

... pero produce un error en tiempo de compilación en:

  • g ++ 6.3.0
  • g ++ 7.0.1 (troncal)
  • clang ++ 5.0.0 (HEAD)

en wandbox

El error producido parece estar relacionado con una sobrecarga del pair operator=(pair&&) restringido pair operator=(pair&&) - desde include/bits/stl_pair.h en el espejo libstdc ++ de GitHub :

pair& operator=(typename conditional< __and_<is_move_assignable<_T1>, is_move_assignable<_T2>>::value, pair&&, __nonesuch&&>::type __p) noexcept(__and_<is_nothrow_move_assignable<_T1>, is_nothrow_move_assignable<_T2>>::value) { first = std::forward<first_type>(__p.first); second = std::forward<second_type>(__p.second); return *this; }

  • Sustituir is_move_assignable<_T2> con std::true_type permite que se compile el código.

  • Mover la definición de Val::operator=(Val&&) antes de /* PLACEHOLDER */ permite compilar el código.

  • Cambiar auto& Val::operator=(Val&&) a Val& Val::operator=(Val&&) permite que el código se compile.

¿Que esta pasando aqui? ¿Es este un defecto de implementación en la última versión de libstdc ++? ¿O las versiones anteriores compilaban incorrectamente el código mal formado?

EDITAR: como descubrió AndyG en su respuesta (ahora eliminada) , el error también ocurre cuando se realiza una llamada a una función vacía antes de invocar emplace :

template <typename TVec> void a(TVec&) { } int main() { std::vector<std::pair<int, Val>> v; a(v); v.emplace(std::begin(v), 0, Val{}); }

Completando a(v); lo anterior evita que se produzca el error en tiempo de compilación. Este comportamiento está presente tanto en g ++ 7 como en clang ++ 5.

en wandbox

Otro caso extraño fue descubierto por Sergey Murzin , y puede ser probado en wandbox :

int main() { std::vector<std::pair<int, Val>> v; v.emplace(v.begin(), 0, Val{}); std::cout << v.back().first << std::endl; }

El código anterior produce un error de compilación. Comentando la línea que contiene std::cout evita que ocurra el error. Este comportamiento está presente tanto en g ++ 7 como en clang ++ 5.