`pair:: operator = (pair &&)` ошибка с `auto &` выведенными операциями перемещения - регрессия libstdС++?

Учитывая эту программу:

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

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

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

  • clang++ 3.9.1

  • clang++ 5.0.0 (HEAD)

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

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

  • g++ 6.2.0
  • clang++ 3.9.1

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

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

на wandbox


Полученная ошибка, по-видимому, связана с ограниченной перегрузкой pair operator=(pair&&) - из include/bits/stl_pair.h в GitHub libstdС++ 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&&) позволяет компилировать код.

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


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

Вывод a(v); выше предотвращает создание ошибки времени компиляции. Это поведение присутствует как в g++ 7, так и в clang++ 5.

на wandbox


Другой странный случай был обнаружен Сергеем Мурзином и может быть протестирован на 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.

Ответы

Ответ 1

Это почти наверняка проблема создания экземпляра. Если вы выполните что-то, что инициирует создание pair<int, Val> определения 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>: вы обращаетесь к его члену данных.)