Date: Tue, 30 Jun 2020 04:21:10 +0200
Hi,
Today I stumbled across the following piece of code (simplified):
class QModelIndex {
QModel *model;
int row, column;
intptr data;
auto salient_members() const {
return std::tie(model, row, column, data);
}
friend bool operator==(QModelIndex lhs, QModelIndex rhs) {
return lhs.salient_members() == rhs.salient_members();
}
friend bool operator<(QModelIndex lhs, QModelIndex rhs) {
return lhs.salient_members() < rhs.salient_members();
}
};
operator<, as implemented, depends on unspecified behavior: it will use
tuple's operator<, which in turn uses operator< on the "model" element
in the tuple, which leads to unspecified behavior in the general case of
unrelated object pointers [expr.rel§4.3].
(The "unspecified" seems to have been removed after C++14, now the
wording says "neither pointer is required to compare greater than the
other", which still sounds pretty unspecified to me. Side note,
[expr.spaceship§7] has identical semantics -- unspecified behavior --
for object pointers, so having it synthesize operator< for the class
above would also be "erroneous".)
When comparing unrelated object pointers, one shall use std::less, but
the problem stays the same; using
std::less{}(lhs.salient_members(), rhs.salient_members())
will not help here, as it will simply call operator< on the tuple.
The point being: std::less does do something "magic" like a
lexicographical comparison applying itself, in turn, to each
corresponding element of the two tuples.
For the same reason, generalizing, something like
std::set<std::tuple<~~~, pointer, ~~~>>
is also broken.
So, should std::less (& co.) have such "magical unwrapping" behavior?
The big problem with this idea is that it's not specific to tuple. In
other words, it opens an endless tunnel: one could similarly reason that
any container-like object (pair, variant, optional, vector, array...)
should also be similarly unwrapped and std::less applied to its contents...
To me, this doesn't sound very appealing: it's not scalable (every
container-like that defines operator< should also specialize less); it
would obviously be a behavioral change; it would come with a teaching
burden; etc.
Has anyone given the matter any thoughts already?
Thanks for reading,
Today I stumbled across the following piece of code (simplified):
class QModelIndex {
QModel *model;
int row, column;
intptr data;
auto salient_members() const {
return std::tie(model, row, column, data);
}
friend bool operator==(QModelIndex lhs, QModelIndex rhs) {
return lhs.salient_members() == rhs.salient_members();
}
friend bool operator<(QModelIndex lhs, QModelIndex rhs) {
return lhs.salient_members() < rhs.salient_members();
}
};
operator<, as implemented, depends on unspecified behavior: it will use
tuple's operator<, which in turn uses operator< on the "model" element
in the tuple, which leads to unspecified behavior in the general case of
unrelated object pointers [expr.rel§4.3].
(The "unspecified" seems to have been removed after C++14, now the
wording says "neither pointer is required to compare greater than the
other", which still sounds pretty unspecified to me. Side note,
[expr.spaceship§7] has identical semantics -- unspecified behavior --
for object pointers, so having it synthesize operator< for the class
above would also be "erroneous".)
When comparing unrelated object pointers, one shall use std::less, but
the problem stays the same; using
std::less{}(lhs.salient_members(), rhs.salient_members())
will not help here, as it will simply call operator< on the tuple.
The point being: std::less does do something "magic" like a
lexicographical comparison applying itself, in turn, to each
corresponding element of the two tuples.
For the same reason, generalizing, something like
std::set<std::tuple<~~~, pointer, ~~~>>
is also broken.
So, should std::less (& co.) have such "magical unwrapping" behavior?
The big problem with this idea is that it's not specific to tuple. In
other words, it opens an endless tunnel: one could similarly reason that
any container-like object (pair, variant, optional, vector, array...)
should also be similarly unwrapped and std::less applied to its contents...
To me, this doesn't sound very appealing: it's not scalable (every
container-like that defines operator< should also specialize less); it
would obviously be a behavioral change; it would come with a teaching
burden; etc.
Has anyone given the matter any thoughts already?
Thanks for reading,
-- Giuseppe D'Angelo | giuseppe.dangelo_at_[hidden] | Senior Software Engineer KDAB (France) S.A.S., a KDAB Group company Tel. France +33 (0)4 90 84 08 53, http://www.kdab.com KDAB - The Qt, C++ and OpenGL Experts
Received on 2020-06-29 21:24:29