C++20 对 reverse_iterator 的更改会破坏此代码吗?
What C++20 change to reverse_iterator is breaking this code?
以下代码可以在 C++11、C++14 和 C++17 中编译,但不能在 C++20 中编译。对标准的哪些更改破坏了此代码?
#include <vector>
#include <utility>
template<typename T>
struct bar
{
typename T::reverse_iterator x;
};
struct foo
{
bar<std::vector<std::pair<foo, foo>*>> x;
};
int main()
{
foo f;
}
错误比较长,但可以概括为:
template argument must be a complete class
这一直是未定义的。 [res.on.functions]/2.5 说:
In particular, the effects are undefined in the following cases:
- [...]
- If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
std::pair
不(也不能)支持不完整的类型。您只是依靠实例化的顺序来解决这个问题。库中的一些变化稍微改变了评估顺序,导致错误。但是未定义的行为是未定义的——它以前可以工作,现在不能工作。
至于为什么具体是 C++20 导致此失败。在 C++20 中,迭代器更改为具有这个新的 iterator_concept
想法。为了实例化它,reverse_iterator
需要确定概念应该是什么。看起来像 this:
#if __cplusplus > 201703L && __cpp_lib_concepts
using iterator_concept
= conditional_t<random_access_iterator<_Iterator>,
random_access_iterator_tag,
bidirectional_iterator_tag>;
using iterator_category
= __detail::__clamp_iter_cat<typename __traits_type::iterator_category,
random_access_iterator_tag>;
#endif
现在,在检查random_access_iterator
的过程中,迭代器概念层次的根奇妙地命名为input_or_output_iterator
,指定在[iterator.concept.iterator]:
template<class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> can-reference;
} &&
weakly_incrementable<I>;
所以,我们必须对我们的迭代器类型做 *i
。在本例中是 __gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> >
。现在,*i
触发 ADL - 因为它当然会触发。并且 ADL 需要实例化所有关联类型 - 因为这些关联类型可能已经注入了可能成为候选人的朋友!
反过来,这需要实例化 pair<foo, foo>
- 因为,我们必须检查。然后在这种特定情况下最终失败了,因为实例化一个类型需要实例化该类型的所有特殊成员函数,而 libstdc++ 为 std::pair
实现条件赋值的方式是使用 Eric Fisellier's trick:
_GLIBCXX20_CONSTEXPR pair&
operator=(typename conditional<
__and_<is_copy_assignable<_T1>,
is_copy_assignable<_T2>>::value,
const pair&, const __nonesuch&>::type __p)
{
first = __p.first;
second = __p.second;
return *this;
}
并且is_copy_assignable
需要完整的类型,而我们没有。
但实际上,即使 pair
在这种情况下使用概念来检查,那仍将涉及实例化相同的类型特征,因此我们最终会处于相同的位置。
故事的寓意是,未定义的行为是未定义的。
以下代码可以在 C++11、C++14 和 C++17 中编译,但不能在 C++20 中编译。对标准的哪些更改破坏了此代码?
#include <vector>
#include <utility>
template<typename T>
struct bar
{
typename T::reverse_iterator x;
};
struct foo
{
bar<std::vector<std::pair<foo, foo>*>> x;
};
int main()
{
foo f;
}
错误比较长,但可以概括为:
template argument must be a complete class
这一直是未定义的。 [res.on.functions]/2.5 说:
In particular, the effects are undefined in the following cases:
- [...]
- If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
std::pair
不(也不能)支持不完整的类型。您只是依靠实例化的顺序来解决这个问题。库中的一些变化稍微改变了评估顺序,导致错误。但是未定义的行为是未定义的——它以前可以工作,现在不能工作。
至于为什么具体是 C++20 导致此失败。在 C++20 中,迭代器更改为具有这个新的 iterator_concept
想法。为了实例化它,reverse_iterator
需要确定概念应该是什么。看起来像 this:
#if __cplusplus > 201703L && __cpp_lib_concepts
using iterator_concept
= conditional_t<random_access_iterator<_Iterator>,
random_access_iterator_tag,
bidirectional_iterator_tag>;
using iterator_category
= __detail::__clamp_iter_cat<typename __traits_type::iterator_category,
random_access_iterator_tag>;
#endif
现在,在检查random_access_iterator
的过程中,迭代器概念层次的根奇妙地命名为input_or_output_iterator
,指定在[iterator.concept.iterator]:
template<class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> can-reference;
} &&
weakly_incrementable<I>;
所以,我们必须对我们的迭代器类型做 *i
。在本例中是 __gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> >
。现在,*i
触发 ADL - 因为它当然会触发。并且 ADL 需要实例化所有关联类型 - 因为这些关联类型可能已经注入了可能成为候选人的朋友!
反过来,这需要实例化 pair<foo, foo>
- 因为,我们必须检查。然后在这种特定情况下最终失败了,因为实例化一个类型需要实例化该类型的所有特殊成员函数,而 libstdc++ 为 std::pair
实现条件赋值的方式是使用 Eric Fisellier's trick:
_GLIBCXX20_CONSTEXPR pair&
operator=(typename conditional<
__and_<is_copy_assignable<_T1>,
is_copy_assignable<_T2>>::value,
const pair&, const __nonesuch&>::type __p)
{
first = __p.first;
second = __p.second;
return *this;
}
并且is_copy_assignable
需要完整的类型,而我们没有。
但实际上,即使 pair
在这种情况下使用概念来检查,那仍将涉及实例化相同的类型特征,因此我们最终会处于相同的位置。
故事的寓意是,未定义的行为是未定义的。