Skip to content

Commit

Permalink
Adds the SortedPair class to drake. (RobotLocomotion#7700)
Browse files Browse the repository at this point in the history
* 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.
edrumwri authored and soonho-tri committed Jan 17, 2018
1 parent ef5676d commit de4d0dd
Showing 7 changed files with 440 additions and 1 deletion.
41 changes: 41 additions & 0 deletions common/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -136,6 +136,9 @@ drake_cc_library(
drake_cc_library(
name = "hash",
hdrs = ["hash.h"],
deps = [
":essential",
],
)

drake_cc_library(
@@ -233,6 +236,30 @@ DRAKE_RESOURCE_SENTINEL = adjust_label_for_drake_hoist(
"//drake:.drake-find_resource-sentinel",
)

drake_cc_library(
name = "is_less_than_comparable",
hdrs = [
"is_less_than_comparable.h",
],
deps = [
":unused",
],
)

drake_cc_library(
name = "sorted_pair",
srcs = [
"sorted_pair.cc",
],
hdrs = [
"sorted_pair.h",
],
deps = [
":hash",
":is_less_than_comparable",
],
)

drake_cc_library(
name = "find_resource",
srcs = [
@@ -598,6 +625,20 @@ drake_cc_googletest(
],
)

drake_cc_googletest(
name = "is_less_than_comparable_test",
deps = [
":is_less_than_comparable",
],
)

drake_cc_googletest(
name = "sorted_pair_test",
deps = [
":sorted_pair",
],
)

drake_cc_googletest(
name = "extract_double_test",
deps = [
2 changes: 1 addition & 1 deletion common/is_cloneable.h
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ struct is_cloneable_helper<
bool value = drake::is_cloneable<Foo>::value;
@endcode
If `Foo` is cloneable, it will evalute to true. It can also be used in
If `Foo` is cloneable, it will evaluate to true. It can also be used in
compile-time tests (e.g., SFINAE and `static_assert`s):
@code
74 changes: 74 additions & 0 deletions common/is_less_than_comparable.h
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
9 changes: 9 additions & 0 deletions common/sorted_pair.cc
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
163 changes: 163 additions & 0 deletions common/sorted_pair.h
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
29 changes: 29 additions & 0 deletions common/test/is_less_than_comparable_test.cc
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
123 changes: 123 additions & 0 deletions common/test/sorted_pair_test.cc
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

0 comments on commit de4d0dd

Please sign in to comment.