考虑以下:
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.0和MSVC 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类型特征上的尝试一致.
此特征通过是因为存在可通过重载解析找到的方法,并且不会删除该方法.
实际上调用它失败了,因为该方法的实现包含对这两种类型不合法的代码.
在C++中,您无法测试实例化方法是否会导致编译错误,您只能测试相当于重载决策的查找解决方案.
C++语言和标准库最初很大程度上依赖于"好吧,如果被调用,方法体只在模板中编译,所以如果正文无效,程序员就会被告知".更现代的C++(标准库内部和外部)使用SFINAE和其他技术在其主体无法编译时使方法"不参与重载解析".
来自其他反向迭代器的反向迭代器的构造函数是旧样式,并且尚未更新为"不参与重载决策"质量.
从n4713,27.5.1.3.1 [reverse.iter.cons]/3:
Run Code Online (Sandbox Code Playgroud)template<class U> constexpr reverse_iterator(const reverse_iterator<U>& u);效果:使用u.current初始化当前值.
请注意,没有提到"不参与重载决议"或类似的单词.
获得你想要的特征的唯一方法是
改变(好吧,修复)C++标准
特例吧
我将离开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_trait是is_assignable除了在反向迭代器,在那里它代替测试包含迭代转让性.
(作为额外的乐趣,反向反向迭代器将适用于此特征).
我曾经不得不做一些非常相似的事情std::vector<T>::operator<,也盲目地打电话T<T,如果那不合法,SFINAE就不会禁用它.
也可能是C++标准库实现可以使得不能编译的构造函数不参与重载决策.这种改变可能会破坏其他形式良好的程序,但只能通过静态断言(可以翻转)或逻辑等效的东西这些荒谬的东西.
这只是一个约束问题.或缺乏.这是一个具有不同特征的简化示例:
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 ++,我们有:
Run Code Online (Sandbox Code Playgroud)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,U即使它不是实际构造的.这个问题使用assignable,但在这种情况下,赋值将通过这个构造函数模板,这就是为什么我在谈论构造.
请注意,这可能是一个libstdc ++ bug,可能是一个疏忽.甚至还有一个评论认为这应该受到限制:
Run Code Online (Sandbox Code Playgroud)/** * A %reverse_iterator across other types can be copied if the * underlying %iterator can be converted to the type of @c current. */