如何检查将const_reverse_iterator分配给reverse_iterator无效?

时间:2018-09-11 15:31:12

标签: c++ c++17

请考虑以下内容:

using vector_type = std::vector<int>;
using const_iterator = typename vector_type::const_iterator;
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
using iterator = typename vector_type::iterator;
using reverse_iterator = typename vector_type::reverse_iterator;

int main()
{
    static_assert(!std::is_assignable_v<iterator, const_iterator>); // passes
    static_assert(!std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // fails
    static_assert(std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // passes
}

我可以检查iterator{} = const_iterator{}的分配是无效的,但不是具有这种类型特征的reverse_iterator{} = const_reverse_iterator{}的分配。

此行为在gcc 9.0.0clang 8.0.0MSVC 19.00.23506之间是一致的

这是不幸的,因为事实是reverse_iterator{} = const_reverse_iterator{}实际上没有使用任何上述编译器进行编译。

我如何可靠地检查此类分配无效?

这种特征类型的行为表示表达式

std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>() 

根据[meta.unary.prop]格式正确,这似乎与我对is_assignable类型特征的尝试一致。

2 个答案:

答案 0 :(得分:5)

此特征通过是因为存在可以通过重载解析找到的方法,并且该方法不会被删除。

实际上调用它失败,因为该方法的实现包含对这两种类型不合法的代码。

在C ++中,您不能测试实例化方法是否会导致编译错误,只能测试等效的重载分辨率来找到解决方案。

C ++语言和标准库最初严重依赖于“嗯,方法主体只有在被调用时才在模板中进行编译,因此,如果主体无效,则会告诉程序员”。更现代的C ++(在标准库的内部和外部)都使用SFINAE和其他技术在其主体无法编译时使方法“不参与重载解析”。

与其他反向迭代器相反的反向迭代器的构造函数是旧样式,尚未更新为“不参与过载解析”质量。

来自n4713,27.5.1.3.1 [reverse.iter.cons] / 3:

  template<class U> constexpr reverse_iterator(const reverse_iterator<U>& u);
     

效果:使用u.current初始化电流。

请注意,没有提及“不参与过载解决方案”或类似词语。


获得想要的这种特征的唯一方法是

  1. 更改(很好,修复)C ++标准

  2. 特殊情况

我将离开1.做运动。对于2.,您知道反向迭代器是正向迭代器的模板。

template<class...Ts>
struct my_trait:std::is_assignable<Ts...> {};

template<class T0, class T1>
struct my_trait<std::reverse_iterator<T0>, std::reverse_iterator<T1>>:
  my_trait<T0, T1>
{};

,现在my_traitis_assignable,但反向迭代器除外,在反向迭代器中它测试包含的迭代器的可分配性。

(作为额外的乐趣,反向反向迭代器可以使用此特征)。

我曾经不得不对std::vector<T>::operator<做一些非常相似的事情,后者也盲目地称为T<T,如果那不合法,SFINAE也不会禁用它。


在某些情况下,C ++标准库实现也可以使不会编译的构造函数不参与重载解析。这种变化可能会破坏原本格式正确的程序,但只能通过静态声明(可能翻转)这样荒谬的事情或逻辑上等效的事情来破坏。

答案 1 :(得分:5)

这只是约束问题。还是缺乏。这是一个具有不同特征的简化示例:

ClassLoader

每当人们谈论对SFINAE友好时,这基本上就是他们所指的东西。确保类型特征给出正确答案。在这里,struct X { template <typename T> X(T v) : i(v) { } int i; }; static_assert(is_constructible_v<X, std::string>); // passes X x("hello"s); // fails 声称可以从任何东西构造出来,但实际上不是。 X实际上并没有实例化整个构造,它只是检查表达式的有效性-只是表面级别的检查。 is_constructible和更高版本的Concept打算解决此问题。

对于libstdc ++,特别是we have

enable_if

这里template<typename _Iter> _GLIBCXX17_CONSTEXPR reverse_iterator(const reverse_iterator<_Iter>& __x) : current(__x.base()) { } 不受限制,因此_Iter对所有is_constructible_v<reverse_iterator<T>, reverse_iterator<U>>true的对都是T,即使实际上不是 < / em>可构建的。这个问题使用可赋值的,但是在这种情况下,赋值将通过此构造函数模板进行,这就是为什么我在谈论构造。


请注意,这可以说是libstdc ++错误,并且可能是疏忽大意。甚至有comment对此应加以限制:

U