`pair :: operator =(pair &&)`错误与`auto&`推导移动操作 - libstdc ++回归?

Vit*_*meo 18 c++ libstdc++ language-lawyer move-semantics c++14

鉴于此计划:

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

/* PLACEHOLDER */

auto& Val::operator=(Val&&) { return *this; }   
Run Code Online (Sandbox Code Playgroud)

/* PLACEHOLDER */用...... 代替

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

...成功编译:

  • g ++ 6.2.0
  • g ++ 6.3.0
  • g ++ 7.0.1(主干)

  • 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);
}
Run Code Online (Sandbox Code Playgroud)

...成功编译:

  • g ++ 6.2.0
  • clang ++ 3.9.1

...但是会产生编译时错误:

  • g ++ 6.3.0
  • g ++ 7.0.1(主干)
  • clang ++ 5.0.0(HEAD)

在wandbox上


产生的错误似乎与受约束的pair operator=(pair&&)重载有关 - 来自include/bits/stl_pair.hGitHub的libstdc ++镜像:

  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;
  }
Run Code Online (Sandbox Code Playgroud)
  • 代is_move_assignable<_T2>与std::true_type允许代码进行编译.

  • 移动Val::operator=(Val&&)之前的定义/* PLACEHOLDER */允许代码编译.

  • 更改auto& Val::operator=(Val&&)为Val& Val::operator=(Val&&)允许代码编译.

这里发生了什么?这是最新版本的libstdc ++中的实现缺陷吗?或者旧版本是否错误地编译了错误的代码?


编辑:正如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{});
}
Run Code Online (Sandbox Code Playgroud)

Commeting出a(v);上述防止编译时错误被产生.g ++ 7和clang ++ 5中都存在此行为.

在wandbox上


另一个奇怪的案例是由Sergey Murzin发现的,可以在wandbox上测试:

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

上面的代码产生编译器错误.注释掉包含的行std::cout可以防止错误发生.g ++ 7和clang ++ 5中都存在此行为.

T.C*_*.C. 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>:您正在访问其数据成员.)