请考虑以下内容:
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.0,clang 8.0.0和MSVC 19.00.23506之间是一致的
这是不幸的,因为事实是reverse_iterator{} = const_reverse_iterator{}
实际上没有使用任何上述编译器进行编译。
这种特征类型的行为表示表达式
std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>()
根据[meta.unary.prop]格式正确,这似乎与我对is_assignable
类型特征的尝试一致。
答案 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初始化电流。
请注意,没有提及“不参与过载解决方案”或类似词语。
获得想要的这种特征的唯一方法是
更改(很好,修复)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>
{};
,现在my_trait
是is_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