如何解决CRTP-ed std::vectors
在 this article 中,我偶然发现了这段晦涩的代码(随意呈现,就像这是一个完全正常的 C++ 代码):
struct Tree : std::vector<Tree> {};
然后创建并比较两棵树(参见the demo):
Tree tree(size_t h)
{
Tree s;
if (h > 0)
s.insert(s.end(),2,tree(h - 1));
return s;
}
int main()
{
size_t h = 15;
Tree s1 = tree(h);
Tree s2 = tree(h);
clock_t t = clock();
s1 < s2;
double d = clock() - t;
d /= CLOCKS_PER_SEC;
std::cout << d << std::endl; // 4.1s
}
经过一番思考后,我意识到这种比较似乎是合法的,并且总是会产生错误。
文章的思路是,由于C++17用std::lexicographical_compare
实现了向量的比较,将两个序列中对应的元素a
和b
比较为{{1} } 和 a<b
(参见 cpppreference 的“可能的实现”部分),像上面的 b<a
这样的深层结构的性能将在深度上呈二次方下降。确实如此。
文章也说三路比较没有这样的问题。但是等等,这正是 C++20 中出现的 Tree
。但这里有一个警告。
此代码无法在 C++20 中编译:
operator<=>
这是我的问题。这个编译错误是编译器中的一个错误,还是这段代码实际上格式错误?如果代码格式错误,是仅适用于 C++20 还是适用于 C++17 和 C++20? (后者意味着此代码只能在 C++17 下偶然编译)。
解决方法
这里的问题是约束递归。
在 C++17 中,vector<T>
的 operator<
有一个先决条件,即 <
是为 T
定义的,但 libstdc++ 没有检查这一点。他们的实现was basically:
template <typename T>
inline bool operator<(vector<T> const& a,vector<T> const& b) {
return lexicographical_compare(a.begin(),a.end(),b.begin(),b.end());
}
这...只是有效。
但在 C++20 中,vector<T>
改为 operator<=>
,它有一个约束:
template <typename T>
// this checks either <=> or <
requires synth_3way_comparable<T>
auto operator<=>(vector<T> const& a,vector<T> const& b) { ... }
这里的问题是:为了弄清楚Tree
是三路可比的,我们必须弄清楚我们是否可以在两个<=>
上调用Tree
,其中找到这个 operator<=>(vector<Tree>,vector<Tree>)
候选,它只在 Tree
是三路可比的时候才定义,所以我们必须弄清楚我们是否可以在两个 <=>
上调用 Tree
,哪个发现...
所以...不起作用,也不能工作。
不幸的是,这甚至不是一个问题:只需将 ==
和 <=>
添加到 Tree
,因为 vector
的 <=>
仍将是候选人并且只是问它是否有效的问题就爆炸了。
因此,这不是在 C++20 中实现比较的可行策略。您将无法像这样继承 vector
。您只需将其添加为成员即可。
此代码在 C++17 中格式良好,在 C++20 中格式错误。
考虑以下两个 2 高的树:
struct Tree : std::vector<Tree> {};
int main() {
Tree s1 = tree(1);
Tree s2 = tree(1);
s1 < s2;
}
在 C++17 中,我们只调用 vector
的 operator<
:
bool operator<(const vector<Tree>& x,const vector<Tree>& y) {
return std::lexicographical_compare(x.begin(),x.end(),y.begin(),y.end());
}
这将调用 std::lexicographical_compare
:
template <class InputIt1,class InputIt2>
bool lexicographical_compare(InputIt1 first1,InputIt1 last1,InputIt2 first2,InputIt2 last2) {
for (; (first1 != last1) && (first2 != last2); ++first1,(void)++first2) {
if (*first1 < *first2) return true;
if (*first2 < *first1) return false;
}
return (first1 == last1) && (first2 != last2);
}
首先比较*first1 < *first2
,*first1
是s1
的左结点,*first2
是s2
的左结点,它们的类型是{ {1}},因此我们返回来比较两个 Tree
。目前,我们没有 Tree
的 operator<
,所以我们回到调用他们基地的 Tree
就像我们第一次做的那样:
operator<
但这一次,两个节点的基数bool operator<(const vector<Tree>& x,y.end());
}
实际上是空的,所以在更深的vector
函数中,我们只是返回false。第二个比较 lexicographical_compare
遵循相同的程序。
到此为止,我们完成了左节点比较,然后我们可以*first2 < *first1
继续右节点比较。
在 C++20 中,++first1,(void)++first2
重写为:
s1 < s2
所以我们称(s1 <=> s2) < 0
的{{1}}:
vector
compare_three_way()
约束 operator<=>
的 auto operator<=>(const vector<Tree>& x,const vector<Tree>& y) {
return std::lexicographical_compare_three_way(
x.begin(),y.end(),std::compare_three_way()
);
}
,即 value_type
必须满足 std::three_way_comparable_with
概念,但我们没有定义任何 {{1} } 为 vector
,所以我们得到 Tree
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。