`pair::operator=(pair&&)` 错误与`auto&` 推导出移动操作-libstdc++ 回归?
`pair::operator=(pair&&)` error with `auto&` deduced move operations - libstdc++ regression?
给出这个程序:
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(主干)
clang++ 3.9.1
- clang++ 5.0.0(头)
将 /* 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(主干)
- clang++ 5.0.0 (HEAD)
产生的错误似乎与受约束的 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;
}
用 std::true_type
替换 is_move_assignable<_T2>
允许代码编译。
将 Val::operator=(Val&&)
的定义移动到 /* PLACEHOLDER */
之前允许代码编译。
将auto& Val::operator=(Val&&)
更改为Val& Val::operator=(Val&&)
允许代码编译。
这是怎么回事?这是最新版本的 libstdc++ 中的实现缺陷吗?还是旧版本错误地编译了格式错误的代码?
编辑:正如 AndyG 在他的 (现已删除) 答案中发现的那样,在调用 [=26 之前调用空函数时也会发生错误=]:
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 中都存在。
Sergey Murzin, and can be tested out 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 中都存在。
这几乎可以肯定是一个 point-of-instantiation 问题。如果您执行的操作会触发 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>
的实例化:您正在访问它的数据成员。)
给出这个程序:
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(主干)
clang++ 3.9.1
- clang++ 5.0.0(头)
将 /* 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(主干)
- clang++ 5.0.0 (HEAD)
产生的错误似乎与受约束的 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;
}
用
std::true_type
替换is_move_assignable<_T2>
允许代码编译。将
Val::operator=(Val&&)
的定义移动到/* PLACEHOLDER */
之前允许代码编译。将
auto& Val::operator=(Val&&)
更改为Val& Val::operator=(Val&&)
允许代码编译。
这是怎么回事?这是最新版本的 libstdc++ 中的实现缺陷吗?还是旧版本错误地编译了格式错误的代码?
编辑:正如 AndyG 在他的 (现已删除) 答案中发现的那样,在调用 [=26 之前调用空函数时也会发生错误=]:
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 中都存在。
Sergey Murzin, and can be tested out 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 中都存在。
这几乎可以肯定是一个 point-of-instantiation 问题。如果您执行的操作会触发 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>
的实例化:您正在访问它的数据成员。)