这里尝试为 Foo
类创建自定义比较器.它将对成员应用一些转换,然后按字典顺序比较它们:
struct Foo {
std::string s;
float x;
std::vector<int> z;
std::unique_ptr<std::deque<double>> p;
friend bool operator<(const Foo& lhs, const Foo& rhs) {
auto make_comparison_object = [](const Foo& foo) {
return std::forward_as_tuple(
foo.s,
-foo.x,
std::accumulate(
foo.z.begin(),
foo.z.end(),
0),
foo.p ? std::make_optional(*foo.p) : std::nullopt);
};
return make_comparison_object(lhs) < make_comparison_object(rhs);
}
};
虽然优雅,但这里有一个问题:右值引用,例如对 -foo.x
结果的引用, 没有充分延长它们指向的右值的生命周期;它们将在 lambda 结束时被销毁。因此 return make_comparison_object(lhs) < make_comparison_object(rhs);
将访问悬挂引用并导致未定义的行为。
我可以看到两种解决方法:
使用
std::make_tuple
而不是std::forward_as_tuple
.这会起作用,但我担心它可能会产生额外的拷贝或移动,特别是我认为它可能会复制传递给std::make_tuple
的任何左值。例如foo.s
.内联 lambda 的内容,如下所示:
return std::forward_as_tuple( lhs.s, -lhs.x, std::accumulate( lhs.z.begin(), lhs.z.end(), 0), lhs.p ? std::make_optional(*lhs.p) : std::nullopt) < std::forward_as_tuple( rhs.s, -rhs.x, std::accumulate( rhs.z.begin(), rhs.z.end(), 0), rhs.p ? std::make_optional(*rhs.p) : std::nullopt);
这也行,但它看起来很糟糕并且违反了 DRY。
有没有更好的方法来完成这种比较?
编辑:这是一些比较建议解决方案的测试代码:
#include <functional>
#include <iostream>
#include <tuple>
#define BEHAVIOR 2
struct A {
A(int data) : data(data) { std::cout << "constructor\n"; }
A(const A& other) : data(other.data) { std::cout << "copy constructor\n"; }
A(A&& other) : data(other.data) { std::cout << "move constructor\n"; }
friend bool operator<(const A& lhs, const A& rhs) {
return lhs.data < rhs.data;
}
int data;
};
A f(const A& a) {
return A{-a.data};
}
struct Foo {
Foo(A a1, A a2) : a1(std::move(a1)), a2(std::move(a2)) {}
A a1;
A a2;
friend bool operator<(const Foo& lhs, const Foo& rhs) {
#if BEHAVIOR == 0
auto make_comparison_object = [](const Foo& foo) {
return std::make_tuple(foo.a1, f(foo.a2));
};
return make_comparison_object(lhs) < make_comparison_object(rhs);
#elif BEHAVIOR == 1
auto make_comparison_object = [](const Foo& foo) {
return std::make_tuple(std::ref(foo.a1), f(foo.a2));
};
return make_comparison_object(lhs) < make_comparison_object(rhs);
#elif BEHAVIOR == 2
return std::forward_as_tuple(lhs.a1, f(lhs.a2))
< std::forward_as_tuple(rhs.a1, f(rhs.a2));
#endif
}
};
int main() {
Foo foo1(A{2}, A{3});
Foo foo2(A{2}, A{1});
std::cout << "===== comparison start =====\n";
auto result = foo1 < foo2;
std::cout << "===== comparison end, result: " << result << " =====\n";
}
您可以在 Wandbox 上试用.结果在 gcc/clang 上是一致的,并且考虑到元组构造中的内容是有意义的:
-
std::make_tuple
: 2份,2次移动 -
std::make_tuple
与std::ref
: 0 份,2 次移动 -
std::forward_as_tuple
内联:0 个拷贝,0 个移动
最佳答案
您可以将 std::make_tuple
与 std::ref
一起使用:
auto make_comparison_object = [](const Foo& foo) {
return std::make_tuple(
std::ref(foo.s),
// ^^^^^^^^
-foo.x,
std::accumulate(
foo.z.begin(),
foo.z.end(),
0),
foo.p ? std::make_optional(*foo.p) : std::nullopt);
};
关于c++ - 使用右值的比较函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51144329/