C++20:自动生成的运算符在派生 类 中不可引用?
C++20: Automatically generated operators are unreferencable in derived classes?
我在编写与宇宙飞船运算符相关的 C++20 时注意到一些相当奇怪的事情。
据我了解,自 C++20 起,比较运算符由编译器自动生成。
但是,我遇到了这个自动生成运算符的有趣问题。
在下面的代码中,我试图定义从 vector<int>::iterator
派生的 MyIterator
。
现在我希望基 class 受到保护并显式公开函数。
所以很自然地,我使用 using
声明来使用基 class 中的成员函数。
然而,编译器抱怨缺少 operator!=
!
发生这种情况是否是因为自动生成的运算符“生成得太晚”?
有趣的是,MyIterator2
定义中显示的解决方法似乎可以解决问题。
我想听听这种奇怪行为的原因。
这是通过宇宙飞船操作员自动生成的操作员的预期行为吗?
或者这是编译器错误或未实现的功能?
编译器版本信息:
[hoge@foobar]$ clang++ --version
clang version 10.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
代码(main.cpp):
#include <vector>
using MyVec = std::vector<int>;
/// Does not compile
class MyIterator : protected MyVec::iterator {
using Base = MyVec::iterator;
// Error
// No member named 'operator!=' in '__gnu_cxx::__normal_iterator<int *,
// std::vector<int, std::allocator<int>>>'clang(no_member)
using Base::operator!=;
};
/// Compiles
class MyIterator2 : protected MyVec::iterator {
using Base = MyVec::iterator;
/// A rather ugly workaround!
auto operator!=(const MyIterator2 &o) const {
return static_cast<const Base &>(*this) != static_cast<const Base &>(o);
}
};
补充说明:
GCC 也以同样的方式拒绝代码。
[hoge@foobar tmp]$ g++ --version
g++ (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[hoge@foobar tmp]$ g++ main.cpp
main.cpp:12:23: error: ‘operator!=’ has not been declared in ‘__gnu_cxx::Base’
12 | using Base::operator!=;
|
std::vector<>::iterator
的比较运算符可能是 (implementation-defined) 并且很可能实现为 non-member 函数
So naturally, I use using declarations to use member functions from the base class. [...]
std::vector
的iterator
类型不需要将其比较运算符实现为成员函数;根据 [vector.overview]/3
The types iterator
and const_iterator
meet the constexpr iterator requirements ([iterator.requirements.general]).
[...]
using iterator = implementation-defined; // see [container.requirements]
恰恰相反,我们可能会注意到所有迭代器适配器,由 [predef.iterators] 管理,将它们的比较运算符指定为 non-member 函数。
因此,GCC 和 Clang 拒绝您的程序都是正确的,并且还提供了关于他们拒绝程序的原因的准确错误消息。与以下简化示例进行比较:
struct A {
int x;
// Non-member operator==.
friend bool operator==(const A&, const A&) = default;
};
struct B {
int x;
// Member operator==.
bool operator==(const B&) const = default;
};
struct ADerived : public A {
using A::operator==; // Error: no MEMBER named 'operator==' in 'A'
};
struct BDerived : public B {
using B::operator==; // OK.
};
As I understand, since C++20, comparison operators are automatically generated by the compiler. However, I have encountered an interesting problem with this automatic generation of operators.
这是不正确的。未生成比较 operators。比较表达式被重写。
即给定:
struct X {
int i;
bool operator==(X const&) const = default;
};
X{2} == X{3}
直接有效,调用默认的 operator==
,它进行 member-wise 比较,因此产生 false
.
X{2} != X{3}
也是一个有效的表达式,但它不会调用任何名为 operator!=
的内容。没有这样的功能。相反,它计算为 !(X{2} == X{3})
,产生 true
。尽管 X{2} != X{3}
是一个有效的表达式,但此处没有任何名为 operator!=
的内容,因此您不能引用具有该名称的任何内容。
So naturally, I use using declarations to use member functions from the base class. However, the compiler complains that operator!=
is missing!
在 C++20 中,我们几乎从来不需要 operator!=
,因此几乎所有这些都已从标准库规范中删除,并且标准库很可能已经通过 #ifdef
-编辑出来。需要解析的代码更少。由于运算符重写,不等式表达式仍然有效,但不再有任何名为 operator!=
的东西。
所以你不能using
它。
但是标准库不再需要 operator!=
的同样原因也适用于您 - 您也不需要它。
请进一步注意,即使在 C++17 中也不能保证 using Base::operator!=;
有效,因为没有义务将 operator!=
编写为成员函数。它本可以写成一个自由函数,然后这无论如何都行不通,即使有一个名为 operator!=
... 的函数就在其他地方。
迭代器,包括std::vector迭代器,不一定是类.
特别是 std vector 的迭代器是原始指针是完全合法的。
从指针继承不会有好结果。
所以使用您的“继承和转发”技术是不安全的。正如您所了解的,它在 std 库版本上也不是稳定的;表达式 it1 != it2
保证有效,但如果库选择,它可以使用 non-member !=
,即使 vector iterators where pointers.
我可能建议的一个技巧是编写一个“索引器”。索引器采用类型 T
并对其进行迭代。我用它来写 iterator-iterators 和写 integer-iterators.
template<class T, bool forward_iterator_traits=false>
struct indexing_iterator {
T value;
T const& operator*() const { return value; }
indexing_iterator& operator++()
{
++value;
return *this;
}
indexing_iterator operator++(int)
{
indexing_iterator tmp(*this); // copy
++*this;
return tmp; // return old value
}
// etc
};
等添加一些迭代器特征转发支持(通过部分特化 iterator_traits<indexing_iterator<T, true>>:iterator_traits<T>
)
现在,indexing_iterator<It>
可以通过 using
声明安全地继承。你必须自己写 operator*
。
你可以编写 for(auto it : iterators_of(Container))
和 for ( auto i : indexes_of(Container))
循环。
双赢。
我在编写与宇宙飞船运算符相关的 C++20 时注意到一些相当奇怪的事情。
据我了解,自 C++20 起,比较运算符由编译器自动生成。 但是,我遇到了这个自动生成运算符的有趣问题。
在下面的代码中,我试图定义从 vector<int>::iterator
派生的 MyIterator
。
现在我希望基 class 受到保护并显式公开函数。
所以很自然地,我使用 using
声明来使用基 class 中的成员函数。
然而,编译器抱怨缺少 operator!=
!
发生这种情况是否是因为自动生成的运算符“生成得太晚”?
有趣的是,MyIterator2
定义中显示的解决方法似乎可以解决问题。
我想听听这种奇怪行为的原因。 这是通过宇宙飞船操作员自动生成的操作员的预期行为吗? 或者这是编译器错误或未实现的功能?
编译器版本信息:
[hoge@foobar]$ clang++ --version
clang version 10.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
代码(main.cpp):
#include <vector>
using MyVec = std::vector<int>;
/// Does not compile
class MyIterator : protected MyVec::iterator {
using Base = MyVec::iterator;
// Error
// No member named 'operator!=' in '__gnu_cxx::__normal_iterator<int *,
// std::vector<int, std::allocator<int>>>'clang(no_member)
using Base::operator!=;
};
/// Compiles
class MyIterator2 : protected MyVec::iterator {
using Base = MyVec::iterator;
/// A rather ugly workaround!
auto operator!=(const MyIterator2 &o) const {
return static_cast<const Base &>(*this) != static_cast<const Base &>(o);
}
};
补充说明:
GCC 也以同样的方式拒绝代码。
[hoge@foobar tmp]$ g++ --version
g++ (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[hoge@foobar tmp]$ g++ main.cpp
main.cpp:12:23: error: ‘operator!=’ has not been declared in ‘__gnu_cxx::Base’
12 | using Base::operator!=;
|
std::vector<>::iterator
的比较运算符可能是 (implementation-defined) 并且很可能实现为 non-member 函数
So naturally, I use using declarations to use member functions from the base class. [...]
std::vector
的iterator
类型不需要将其比较运算符实现为成员函数;根据 [vector.overview]/3
The types
iterator
andconst_iterator
meet the constexpr iterator requirements ([iterator.requirements.general]).[...]
using iterator = implementation-defined; // see [container.requirements]
恰恰相反,我们可能会注意到所有迭代器适配器,由 [predef.iterators] 管理,将它们的比较运算符指定为 non-member 函数。
因此,GCC 和 Clang 拒绝您的程序都是正确的,并且还提供了关于他们拒绝程序的原因的准确错误消息。与以下简化示例进行比较:
struct A {
int x;
// Non-member operator==.
friend bool operator==(const A&, const A&) = default;
};
struct B {
int x;
// Member operator==.
bool operator==(const B&) const = default;
};
struct ADerived : public A {
using A::operator==; // Error: no MEMBER named 'operator==' in 'A'
};
struct BDerived : public B {
using B::operator==; // OK.
};
As I understand, since C++20, comparison operators are automatically generated by the compiler. However, I have encountered an interesting problem with this automatic generation of operators.
这是不正确的。未生成比较 operators。比较表达式被重写。
即给定:
struct X {
int i;
bool operator==(X const&) const = default;
};
X{2} == X{3}
直接有效,调用默认的 operator==
,它进行 member-wise 比较,因此产生 false
.
X{2} != X{3}
也是一个有效的表达式,但它不会调用任何名为 operator!=
的内容。没有这样的功能。相反,它计算为 !(X{2} == X{3})
,产生 true
。尽管 X{2} != X{3}
是一个有效的表达式,但此处没有任何名为 operator!=
的内容,因此您不能引用具有该名称的任何内容。
So naturally, I use using declarations to use member functions from the base class. However, the compiler complains that
operator!=
is missing!
在 C++20 中,我们几乎从来不需要 operator!=
,因此几乎所有这些都已从标准库规范中删除,并且标准库很可能已经通过 #ifdef
-编辑出来。需要解析的代码更少。由于运算符重写,不等式表达式仍然有效,但不再有任何名为 operator!=
的东西。
所以你不能using
它。
但是标准库不再需要 operator!=
的同样原因也适用于您 - 您也不需要它。
请进一步注意,即使在 C++17 中也不能保证 using Base::operator!=;
有效,因为没有义务将 operator!=
编写为成员函数。它本可以写成一个自由函数,然后这无论如何都行不通,即使有一个名为 operator!=
... 的函数就在其他地方。
迭代器,包括std::vector迭代器,不一定是类.
特别是 std vector 的迭代器是原始指针是完全合法的。
从指针继承不会有好结果。
所以使用您的“继承和转发”技术是不安全的。正如您所了解的,它在 std 库版本上也不是稳定的;表达式 it1 != it2
保证有效,但如果库选择,它可以使用 non-member !=
,即使 vector iterators where pointers.
我可能建议的一个技巧是编写一个“索引器”。索引器采用类型 T
并对其进行迭代。我用它来写 iterator-iterators 和写 integer-iterators.
template<class T, bool forward_iterator_traits=false>
struct indexing_iterator {
T value;
T const& operator*() const { return value; }
indexing_iterator& operator++()
{
++value;
return *this;
}
indexing_iterator operator++(int)
{
indexing_iterator tmp(*this); // copy
++*this;
return tmp; // return old value
}
// etc
};
等添加一些迭代器特征转发支持(通过部分特化 iterator_traits<indexing_iterator<T, true>>:iterator_traits<T>
)
现在,indexing_iterator<It>
可以通过 using
声明安全地继承。你必须自己写 operator*
。
你可以编写 for(auto it : iterators_of(Container))
和 for ( auto i : indexes_of(Container))
循环。
双赢。