2017-01-31 2 views
18

Учитывая эту программу:`pair :: operator = (pair &&)` ошибка с `auto &` выведенными операциями перемещения - регрессия libstdC++?

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

/* PLACEHOLDER */ 

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

Подстановка /* PLACEHOLDER */ с ...

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

... успешно компилирует на:

  • г ++ 6.2.0
  • г ++ 6.3.0
  • г ++ 7.0.1 (ствол)

  • лязг ++ 3.9.1

  • лязг ++ 5.0.0 (ГОЛОВА)

on wandbox


Подставив /* PLACEHOLDER */ с ...

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

... успешно компилирует на:

  • г ++ 6.2.0
  • лязгом ++ 3.9.1

... но выдает ошибку во время компиляции на:

  • g ++ 6.3.0
  • г ++ 7.0.1 (ствол)
  • лязг ++ 5.0.0 (ГОЛОВКА)

on wandbox


Полученная ошибка, как представляется, связано с ограничениями pair operator=(pair&&) перегрузки - 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; 
    } 
  • Подстановка is_move_assignable<_T2> с std::true_type позволяет скомпилировать код.

  • Перемещение определения Val::operator=(Val&&) до /* PLACEHOLDER */ позволяет скомпилировать код.

  • Изменение auto& Val::operator=(Val&&) до Val& Val::operator=(Val&&) позволяет скомпилировать код.

Что здесь происходит? Является ли это дефектом реализации в последней версии libstdC++? Или старые версии неправильно компилировали плохо сформированный код?


EDIT: в AndyG обнаружили в его (теперь удалены) ответ, ошибка также возникает, когда вызов пустой функции производится перед вызовом 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 из a(v); выше, предотвращает создание ошибки времени компиляции. Это поведение присутствует как в g ++ 7, так и в clang ++ 5.

on wandbox


Другой странный случай был обнаружен Sergey Murzin, и может быть опробовано on wandbox:

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

Код выше выдает ошибку компилятора. Комментирование строки, содержащей std::cout, предотвращает возникновение ошибки. Это поведение присутствует как в g ++ 7, так и в clang ++ 5.

+2

[dcl.spec.auto]/11 «Если тип объекта с неопределенным типом-заполнителем необходим для определения типа выражение, программа плохо сформирована ». Поэтому я думаю, что ваша первая программа плохо сформирована. (Стандарт не говорит «никакой диагностической необходимости», поэтому должна быть диагностика). –

+1

Несомненно, это относится к требованию, чтобы компилятор учитывал только функции, которые были определены до того, как шаблон, который он пытается создать. Может все еще быть ошибкой, поскольку я не уверен на 100% правил. – NathanOliver

+0

Я думаю, мы могли бы спорить о том, считает ли использование _T2 в 'парном :: operator =' экземпляре как «определение типа выражения» –

ответ

9

Это почти наверняка проблема создания экземпляра. Если вы выполните что-то, что инициирует создание pair<int, Val> в main, вы получите ошибку. В противном случае он создается только тогда, когда создается экземпляр vector::emplace, который относится к рассматриваемой реализации, до конца блока перевода (что разрешено, см. [temp.point]/8), после чего оператор присваивания полностью определен и может быть вызван.

a(v) инициирует создание определения pair<int, Val>, потому что это необходимо для ADL. Если вы пишете ::a(v) или (a)(v) (оба подавляют ADL), тогда ошибка исчезает. (Очевидно, что a.back().first требует создания экземпляра pair<int, Val>: вы обращаетесь к его элементу данных.)