forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds the SortedPair class to drake. (RobotLocomotion#7700)
* Initial commit. * Lint brush. * Modified the STL vector test. * Provided detail on what template types are allowed. * Checkpoint. * Introduced static tests. * Added proper hashing approach. * Updates from reviewer comments. * Updates from feature review. * Fixed spurious "Foo:". * Fixed gcc compilation problem. * Updates after platform review. * Checkpoint. * More updates from platform review. * More miscellaneous updates from platform review. * Fixed bug caught in platform review.
1 parent
ef5676d
commit de4d0dd
Showing
7 changed files
with
440 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
#pragma once | ||
|
||
#include <type_traits> | ||
|
||
#include "drake/common/unused.h" | ||
|
||
namespace drake { | ||
|
||
#ifndef DRAKE_DOXYGEN_CXX | ||
|
||
namespace is_less_than_comparable_detail { | ||
|
||
// Default case; assumes that a class is *not* less-than comparable. | ||
template <typename T, typename = void> | ||
struct is_less_than_comparable_helper : std::false_type { }; | ||
|
||
// Special sauce for SFINAE. Only compiles if it can finds the method | ||
// `operator<`. If this exists, the is_less_than_comparable implicitly | ||
// prefers this overload over the default overload. | ||
template <typename T> | ||
struct is_less_than_comparable_helper<T, typename std::enable_if<true, | ||
decltype(unused(std::declval<T&>() < std::declval<T&>()), | ||
(void)0)>::type> : std::true_type {}; | ||
|
||
} // namespace is_less_than_comparable_detail | ||
|
||
/** @endcond */ | ||
|
||
/** | ||
@anchor is_less_than_comparable_doc | ||
Provides method for determining at run time if a class is comparable using | ||
the less-than operator (<). | ||
__Usage__ | ||
This gets used like `type_traits` functions (e.g., `is_copy_constructible`, | ||
`is_same`, etc.) To determine if a class is less-than comparable simply invoke: | ||
@code | ||
bool value = drake::is_less_than_comparable<Foo>::value; | ||
@endcode | ||
If `Foo` is less-than comparable, it will evaluate to true. It can also be used | ||
in compile-time tests (e.g., SFINAE and `static_assert`s): | ||
@code | ||
static_assert(is_less_than_comparable<Foo>::value, "This method requires its " | ||
"classes to be less-than comparable."); | ||
@endcode | ||
__Definition of "less-than comparability"__ | ||
To be less-than comparable, the class `Foo` must have a public method of the | ||
form: | ||
@code | ||
bool Foo::operator<(const Foo&) const; | ||
@endcode | ||
or a definition external to the class of the form: | ||
@code | ||
bool operator<(const Foo&, const Foo&); | ||
@endcode | ||
@tparam T The class to test for less-than comparability. | ||
*/ | ||
template <typename T> | ||
using is_less_than_comparable = | ||
is_less_than_comparable_detail::is_less_than_comparable_helper<T, void>; | ||
|
||
} // namespace drake | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#include "drake/common/sorted_pair.h" | ||
|
||
namespace drake { | ||
|
||
// Some template instantiations. | ||
template struct SortedPair<double>; | ||
template struct SortedPair<int>; | ||
|
||
} // namespace drake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
#pragma once | ||
|
||
#include <algorithm> | ||
#include <utility> | ||
|
||
#include "drake/common/drake_copyable.h" | ||
#include "drake/common/hash.h" | ||
#include "drake/common/is_less_than_comparable.h" | ||
|
||
/// @file | ||
/// Provides drake::MakeSortedPair and drake::SortedPair for storing two | ||
/// values of a certain type in sorted order. | ||
|
||
namespace drake { | ||
|
||
/// This class is similar to the std::pair class. However, this class uses a | ||
/// pair of homogeneous types (std::pair can use heterogeneous types) and sorts | ||
/// the first and second values such that the first value is less than or equal | ||
/// to the second one). Note that the sort is a stable one. Thus the SortedPair | ||
/// class is able to be used to generate keys (e.g., for std::map, etc.) from | ||
/// pairs of objects. | ||
/// | ||
/// @tparam T A template type that provides `operator<` and supports default | ||
/// construction. | ||
template <class T> | ||
struct SortedPair { | ||
DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(SortedPair) | ||
static_assert(is_less_than_comparable<T>::value, "SortedPair can only be used" | ||
"with types that can be compared using the less-than operator " | ||
"(operator<)."); | ||
|
||
/// The default constructor creates `first()` and `second()` using their | ||
/// respective default constructors. | ||
SortedPair() = default; | ||
|
||
/// Rvalue reference constructor, permits constructing with std::unique_ptr | ||
/// types, for example. | ||
SortedPair(T&& a, T&& b) { | ||
if (b < a) { | ||
first_ = std::move(b); | ||
second_ = std::move(a); | ||
} else { | ||
first_ = std::move(a); | ||
second_ = std::move(b); | ||
} | ||
} | ||
|
||
/// Constructs a %SortedPair from two objects. | ||
SortedPair(const T& a, const T& b) : first_(a), second_(b) { | ||
if (second_ < first_) | ||
std::swap(first_, second_); | ||
} | ||
|
||
/// Type-converting copy constructor. | ||
template <class U> | ||
SortedPair(SortedPair<U>&& u) : first_{std::forward<T>(u.first())}, | ||
second_{std::forward<T>(u.second())} {} | ||
|
||
/// Resets the stored objects. | ||
template <class U> | ||
void set(U&& a, U&& b) { | ||
first_ = std::forward<U>(a); | ||
second_ = std::forward<U>(b); | ||
if (second_ < first_) | ||
std::swap(first_, second_); | ||
} | ||
|
||
/// Gets the first (according to `operator<`) of the objects. | ||
const T& first() const { return first_; } | ||
|
||
/// Gets the second (according to `operator<`) of the objects. | ||
const T& second() const { return second_; } | ||
|
||
/// Swaps `this` and `t`. | ||
void Swap(drake::SortedPair<T>& t) { | ||
std::swap(t.first_, first_); | ||
std::swap(t.second_, second_); | ||
} | ||
|
||
/// Implements the @ref hash_append concept. | ||
template <class HashAlgorithm> | ||
friend void hash_append(HashAlgorithm& hasher, const SortedPair& p) noexcept { | ||
using drake::hash_append; | ||
hash_append(hasher, p.first_); | ||
hash_append(hasher, p.second_); | ||
} | ||
|
||
private: | ||
T first_{}; // The first of the two objects, according to operator<. | ||
T second_{}; // The second of the two objects, according to operator<. | ||
}; | ||
|
||
/// Two pairs of the same type are equal iff their members are equal after | ||
/// sorting. | ||
template <class T> | ||
inline bool operator==(const SortedPair<T>& x, const SortedPair<T>& y) { | ||
return !(x < y) && !(y < x); | ||
} | ||
|
||
/// Compares two pairs using lexicographic ordering. | ||
template <class T> | ||
inline bool operator<(const SortedPair<T>& x, const SortedPair<T>& y) { | ||
return std::tie(x.first(), x.second()) < std::tie(y.first(), y.second()); | ||
} | ||
|
||
/// Determine whether two SortedPair objects are not equal using `operator==`. | ||
template <class T> | ||
inline bool operator!=( | ||
const SortedPair<T>& x, const SortedPair<T>& y) { | ||
return !(x == y); | ||
} | ||
|
||
/// Determines whether `x > y` using `operator<`. | ||
template <class T> | ||
inline bool operator>(const SortedPair<T>& x, const SortedPair<T>& y) { | ||
return y < x; | ||
} | ||
|
||
/// Determines whether `x <= y` using `operator<`. | ||
template <class T> | ||
inline bool operator<=(const SortedPair<T>& x, const SortedPair<T>& y) { | ||
return !(y < x); | ||
} | ||
|
||
/// Determines whether `x >= y` using `operator<`. | ||
template <class T> | ||
inline bool | ||
operator>=(const SortedPair<T>& x, const SortedPair<T>& y) { | ||
return !(x < y); | ||
} | ||
|
||
/// @brief A convenience wrapper for creating a sorted pair from two objects. | ||
/// @param x The first_ object. | ||
/// @param y The second_ object. | ||
/// @return A newly-constructed SortedPair object. | ||
template <class T> | ||
inline constexpr SortedPair<typename std::decay<T>::type> | ||
MakeSortedPair(T&& x, T&& y) { | ||
return SortedPair< | ||
typename std::decay<T>::type>(std::forward<T>(x), std::forward<T>(y)); | ||
} | ||
|
||
} // namespace drake | ||
|
||
namespace std { | ||
|
||
/// Implements std::swap(). | ||
template <class T> | ||
void swap(drake::SortedPair<T>& t, drake::SortedPair<T>& u) { | ||
t.Swap(u); | ||
} | ||
|
||
/// Provides std::hash<SortedPair<T>>. | ||
template <class T> | ||
struct hash<drake::SortedPair<T>> | ||
: public drake::DefaultHash {}; | ||
#if defined(__GLIBCXX__) | ||
// https://gcc.gnu.org/onlinedocs/libstdc++/manual/unordered_associative.html | ||
template <class T> | ||
struct __is_fast_hash<hash<drake::SortedPair<T>>> : std::false_type {}; | ||
#endif | ||
|
||
} // namespace std |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#include "drake/common/is_less_than_comparable.h" | ||
|
||
#include <gtest/gtest.h> | ||
|
||
namespace drake { | ||
namespace { | ||
|
||
// Dummy operator< for checking is_less_than_comparable(). | ||
struct Z {}; | ||
bool operator<(const Z&, const Z&) { return true; } | ||
|
||
// Verifies that this class is comparable using the less than operator. | ||
GTEST_TEST(LessThanComparable, RunTime) { | ||
// Necessary to work around a Clang error. | ||
const Z z; | ||
operator<(z, z); | ||
|
||
struct X {}; | ||
struct Y { | ||
bool operator<(const Y&) const { return true; } | ||
}; | ||
EXPECT_TRUE(drake::is_less_than_comparable<int>::value); | ||
EXPECT_TRUE(drake::is_less_than_comparable<Y>::value); | ||
EXPECT_FALSE(drake::is_less_than_comparable<X>::value); | ||
EXPECT_TRUE(drake::is_less_than_comparable<Z>::value); | ||
} | ||
|
||
} // namespace | ||
} // namespace drake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
#include "drake/common/sorted_pair.h" | ||
|
||
#include <unordered_map> | ||
#include <vector> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
namespace drake { | ||
namespace { | ||
|
||
// Verifies behavior of the default constructor. | ||
GTEST_TEST(SortedPair, Default) { | ||
SortedPair<int> x; | ||
EXPECT_EQ(x.first(), 0); | ||
EXPECT_EQ(x.second(), 0); | ||
|
||
SortedPair<double> y; | ||
EXPECT_EQ(y.first(), 0.0); | ||
EXPECT_EQ(y.second(), 0.0); | ||
} | ||
|
||
// Verifies sorting occurs. | ||
GTEST_TEST(SortedPair, Values) { | ||
SortedPair<int> x(3, 2); | ||
EXPECT_EQ(x.first(), 2); | ||
EXPECT_EQ(x.second(), 3); | ||
} | ||
|
||
// Verifies that the type casting copy constructor works as desired. | ||
GTEST_TEST(SortedPair, Casting) { | ||
SortedPair<double> x = SortedPair<int>(3, 2); | ||
EXPECT_EQ(x.first(), 2.0); | ||
EXPECT_EQ(x.second(), 3.0); | ||
} | ||
|
||
// Verifies that 'set()' works properly. | ||
GTEST_TEST(SortedPair, Set) { | ||
SortedPair<double> x; | ||
x.set(1.0, 2.0); | ||
EXPECT_EQ(x.first(), 1.0); | ||
EXPECT_EQ(x.second(), 2.0); | ||
x.set(5.0, 3.0); | ||
EXPECT_EQ(x.first(), 3.0); | ||
EXPECT_EQ(x.second(), 5.0); | ||
} | ||
|
||
// Verifies that the move assignment operator and move constructor work. | ||
GTEST_TEST(SortedPair, Move) { | ||
auto a = std::make_unique<int>(2); | ||
auto b = std::make_unique<int>(1); | ||
SortedPair<std::unique_ptr<int>> y(std::move(a), std::move(b)); | ||
SortedPair<std::unique_ptr<int>> x; | ||
x = std::move(y); | ||
EXPECT_TRUE(x.first() < x.second()); | ||
EXPECT_EQ(y.first().get(), nullptr); | ||
EXPECT_EQ(y.second().get(), nullptr); | ||
y = SortedPair<std::unique_ptr<int>>(std::move(x)); | ||
EXPECT_TRUE(y.first() < y.second()); | ||
EXPECT_EQ(x.first().get(), nullptr); | ||
EXPECT_EQ(x.second().get(), nullptr); | ||
} | ||
|
||
// Checks the assignment operator. | ||
GTEST_TEST(SortedPair, Assignment) { | ||
SortedPair<int> x; | ||
SortedPair<int> y(3, 2); | ||
EXPECT_EQ(&(x = y), &x); | ||
EXPECT_EQ(x.first(), 2.0); | ||
EXPECT_EQ(x.second(), 3.0); | ||
} | ||
|
||
// Checks the equality operator. | ||
GTEST_TEST(SortedPair, Equality) { | ||
SortedPair<int> x(1, 2), y(2, 1); | ||
EXPECT_EQ(x, y); | ||
} | ||
|
||
// Checks the comparison operators. | ||
GTEST_TEST(SortedPair, Comparison) { | ||
SortedPair<int> x(1, 2), y(2, 2); | ||
EXPECT_FALSE(x < x); | ||
EXPECT_FALSE(x > x); | ||
EXPECT_TRUE(x <= x); | ||
EXPECT_TRUE(x >= x); | ||
EXPECT_TRUE(x < y); | ||
} | ||
|
||
// Checks the swap function. | ||
GTEST_TEST(SortedPair, Swap) { | ||
SortedPair<int> x(1, 2), y(3, 4); | ||
std::swap(x, y); | ||
EXPECT_EQ(x.first(), 3); | ||
EXPECT_EQ(x.second(), 4); | ||
EXPECT_EQ(y.first(), 1); | ||
EXPECT_EQ(y.second(), 2); | ||
} | ||
|
||
// Checks hash keys. | ||
GTEST_TEST(SortedPair, Hash) { | ||
SortedPair<int> x(1, 2), y(2, 4); | ||
std::unordered_map<SortedPair<int>, int> hash; | ||
hash[x] = 11; | ||
hash[y] = 13; | ||
EXPECT_EQ(hash[x], 11); | ||
EXPECT_EQ(hash[y], 13); | ||
} | ||
|
||
// Checks expansion with STL vector. | ||
GTEST_TEST(SortedPair, VectorExp) { | ||
std::vector<std::unique_ptr<SortedPair<int>>> v; | ||
v.emplace_back(std::make_unique<SortedPair<int>>(1, 2)); | ||
v.resize(v.capacity() + 1); | ||
EXPECT_EQ(v.front()->first(), 1); | ||
EXPECT_EQ(v.front()->second(), 2); | ||
} | ||
|
||
// Tests the MakeSortedPair operator. | ||
GTEST_TEST(SortedPair, MakeSortedPair) { | ||
EXPECT_EQ(SortedPair<int>(1, 2), MakeSortedPair(1, 2)); | ||
} | ||
|
||
} // namespace | ||
} // namespace drake |