2017-01-31 40 views
18

Biorąc pod uwagę ten program:`pair :: operator = (pair &&)` błąd z `auto &` wyprowadzonymi operacjami przenoszenia - regresja libstdC++?

struct Val 
{ 
    Val() = default; 
    Val(Val&&) = default; 
    auto& operator=(Val&&); 
}; 

/* PLACEHOLDER */ 

auto& Val::operator=(Val&&) { return *this; } 

zastępując /* PLACEHOLDER */ z ...

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

... kompiluje pomyślnie:

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

  • dzyń ++ 3.9.1

  • dzyń ++ 5.0.0 (HEAD)

on wandbox


Podstawiając /* PLACEHOLDER */ z ...

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

... kompiluje pomyślnie:

  • g ++ 6.2.0
  • Clang ++ 3.9.1

... ale powoduje błąd kompilacji na:

  • g ++ 6.3.0
  • g ++ 7.0.1 (pień)
  • dzyń ++ 5.0.0 (HEAD)

on wandbox


Wytworzona błąd wydaje się być związane z nieswobodnego pair operator=(pair&&) przeciążeniem - from include/bits/stl_pair.h on GitHub's libstdc++ mirror :

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; 
    } 
  • Podstawienie is_move_assignable<_T2> za pomocą std::true_type pozwala na kod do kompilacji.

  • Przeniesienie definicji Val::operator=(Val&&) przed /* PLACEHOLDER */ umożliwia kod do kompilacji.

  • Zmiana auto& Val::operator=(Val&&) na Val& Val::operator=(Val&&) umożliwia kod do kompilacji.

Co się tutaj dzieje? Czy jest to defekt implementacji w najnowszej wersji libstdC++? A może starsze wersje nieprawidłowo kompilowały źle sformułowany kod?


EDIT: jak AndyG odkryto w jego (obecnie skreślony) odpowiedź, błąd występuje również, gdy wezwanie do pustej funkcji jest wykonany przed wywołaniem 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{}); 
} 

Commeting się a(v); powyżej zapobiega powstawaniu błędu podczas kompilacji. To zachowanie występuje zarówno w g ++ 7, jak i clang ++ 5.

on wandbox


Kolejna dziwna sprawa została odkryta przez Sergey Murzin i mogą być testowane na on wandbox:

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

Powyższy kod powoduje błąd kompilatora. Komentowanie linii zawierającej std::cout zapobiega wystąpieniu błędu. To zachowanie występuje zarówno w g ++ 7, jak i clang ++ 5.

+2

[dcl.spec.auto]/11 "Jeśli typ obiektu z nieuprawnionym typem zastępczym jest potrzebny do określenia typu wypowiedzi, program jest źle sformułowany. " Więc myślę, że twój pierwszy program jest źle sformułowany. (Standard nie mówi "nie wymaga diagnostyki", więc powinna istnieć diagnoza). –

+1

Dość pewna jest to, że kompilator bierze pod uwagę tylko funkcje zdefiniowane przed szablonem, który próbuje utworzyć. Nadal może być błędem, ponieważ nie jestem w 100% pewien reguł. – NathanOliver

+0

Myślę, że moglibyśmy spierać się o to, czy użycie _T2 w instancji 'pair :: operator =' liczy się jako "określenie typu wyrażenia" –

Odpowiedz

9

Jest to prawie na pewno problem związany z tworzeniem punktów. Jeśli zrobisz coś, co wyzwala utworzenie pair<int, Val> z definicji w main, wtedy pojawi się błąd. W przeciwnym razie jest tworzona tylko wtedy, gdy zostanie utworzona instancja vector::emplace, której rozpatrywane implementacje odradzają się do końca jednostki tłumaczeniowej (która jest dozwolona, ​​patrz [temp.point]/8), w którym to momencie operator przypisania jest w pełni zdefiniowany i możliwy do wywołania.

a(v) uruchamia tworzenie definicji pair<int, Val>, ponieważ jest potrzebna do ADL. Jeśli napiszesz ::a(v) lub (a)(v) (oba pomijają ADL), błąd zniknie. (Oczywiście, a.back().first wymaga instancji pair<int, Val>: uzyskujesz dostęp do jej elementu danych.)