如何检查const_reverse_iterator到reverse_iterator的赋值是无效的?

And*_*dyG 10 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
}
Run Code Online (Sandbox Code Playgroud)

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

此行为在gcc 9.0.0,clang 8.0.0MSVC 19.00.23506中是一致的

这很不幸,因为实际情况是reverse_iterator{} = const_reverse_iterator{}实际上并没有使用任何上述编译器进行编译.

如何可靠地检查此类作业是否无效?

类型特征的这种行为意味着表达式

std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>() 
Run Code Online (Sandbox Code Playgroud)

根据[meta.unary.prop]很好地形成,这看起来与我自己在is_assignable类型特征上的尝试一致.

Yak*_*ont 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);
Run Code Online (Sandbox Code Playgroud)

效果:使用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>
{};
Run Code Online (Sandbox Code Playgroud)

现在my_traitis_assignable除了在反向迭代器,在那里它代替测试包含迭代转让性.

(作为额外的乐趣,反向反向迭代器将适用于此特征).

我曾经不得不做一些非常相似的事情std::vector<T>::operator<,也盲目地打电话T<T,如果那不合法,SFINAE就不会禁用它.


也可能是C++标准库实现可以使得不能编译的构造函数不参与重载决策.这种改变可能会破坏其他形式良好的程序,但只能通过静态断言(可以翻转)或逻辑等效的东西这些荒谬的东西.


Bar*_*rry 5

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

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

每当人们谈论SFINAE友好时 - 这基本上就是他们所指的.确保类型特征给出正确的答案.在这里,X声称是从什么constructible -但实际上它不是.is_constructible实际上并没有实例化整个结构,只是检查表达式的有效性 - 它只是一个表面级别的检查.这是enable_if后来的概念旨在解决的问题.

对于libstdc ++,我们有:

template<typename _Iter>
_GLIBCXX17_CONSTEXPR
reverse_iterator(const reverse_iterator<_Iter>& __x)
: current(__x.base()) { }
Run Code Online (Sandbox Code Playgroud)

有上没有约束_Iter在这里,所以is_constructible_v<reverse_iterator<T>, reverse_iterator<U>>true将所有对T,U即使它不是实际构造的.这个问题使用assignable,但在这种情况下,赋值将通过这个构造函数模板,这就是为什么我在谈论构造.


请注意,这可能是一个libstdc ++ bug,可能是一个疏忽.甚至还有一个评论认为这应该受到限制:

/**
 *  A %reverse_iterator across other types can be copied if the
 *  underlying %iterator can be converted to the type of @c current.
 */
Run Code Online (Sandbox Code Playgroud)

  • @ Yakk-AdamNevraumont [它没有](http://eel.is/c++draft/reverse.iter.cons)...那可能是一个标准的库bug. (3认同)