【问题标题】:What C++20 change to reverse_iterator is breaking this code?对 reverse_iterator 的 C++20 更改是什么破坏了这段代码?
【发布时间】:2020-11-21 12:07:28
【问题描述】:

以下代码在 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;
}

错误比较长,但可以总结为:

模板参数必须是一个完整的类

【问题讨论】:

    标签: c++ language-lawyer c++20 reverse-iterator


    【解决方案1】:

    这始终是未定义的。 [res.on.functions]/2.5 说:

    特别是在以下情况下效果是不确定的:

    • [...]
    • 如果在实例化模板组件或评估概念时将不完整类型 ([basic.types]) 用作模板参数,除非该组件特别允许。

    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&lt;std::pair&lt;foo, foo&gt;**, std::vector&lt;std::pair&lt;foo, foo&gt;*&gt; &gt; 。现在,*i 触发 ADL - 因为它当然会触发。而且 ADL 需要实例化所有关联类型 - 因为这些关联类型可能已经注入了可能成为候选对象的朋友!

    这反过来又需要实例化pair&lt;foo, foo&gt; - 因为,我们必须检查。然后在这种特定情况下最终失败,因为实例化一个类型需要实例化该类型的所有特殊成员函数,而 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 在这种情况下使用概念来检查,这仍然会涉及实例化相同的类型特征,因此我们最终会处于相同的位置。

    故事的寓意是,未定义的行为是未定义的。

    【讨论】:

    • 注意参数不是一对,它是一个指向一对的指针。忽略指针算术,指向不完整类型的指针是完整类型,不是吗?
    • @Pubby:但是通过输入文本std::pair&lt;T, T&gt;,您要求编译器实例化该模板。并且该实例化需要T 是一个完整的类型。 (在大多数情况下)在哪里键入该文本并不重要。
    • @NicolBolas 我认为这不是真的。从字面上考虑任何模板节点类coliru.stacked-crooked.com/a/655220ab33da9f2a
    • @Pubby:模板node 不以任何需要它完整的方式使用Tpair 确实如此。当我说“那个实例化”时,我的意思是“std::pair 的实例化”,而不是一般的实例化。
    • @Pubby 我最近正好问过this。至少那里的结论是它是UB。
    猜你喜欢
    • 2011-09-27
    • 1970-01-01
    • 2012-10-08
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    相关资源
    最近更新 更多