Commit 5f517beb authored by Tom Jackson's avatar Tom Jackson Committed by Sara Golemon

Disabling conversion with contained value for Optional

Summary: Comparisons with values can lead to confusion, especially when the value itself is truthy. To avoid this confusion, I'm disabling comparison with contained value. Note that Optionals can still be constructed and assigned these values, but comparsion must be done separately for the container and the contained.

Test Plan: Unit tests, contbuild

Reviewed By: tudorb@fb.com

FB internal diff: D783621
parent e2b1c08a
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#include <boost/operators.hpp> #include <boost/operators.hpp>
namespace folly { namespace folly {
namespace detail { struct NoneHelper {}; } namespace detail { struct NoneHelper {}; }
...@@ -81,10 +82,7 @@ const None none = nullptr; ...@@ -81,10 +82,7 @@ const None none = nullptr;
#endif // __GNUC__ #endif // __GNUC__
template<class Value> template<class Value>
class Optional : boost::totally_ordered<Optional<Value>, class Optional {
boost::totally_ordered<Optional<Value>, Value>> {
typedef void (Optional::*bool_type)() const;
void truthy() const {};
public: public:
static_assert(!std::is_reference<Value>::value, static_assert(!std::is_reference<Value>::value,
"Optional may not be used with reference types"); "Optional may not be used with reference types");
...@@ -110,7 +108,7 @@ class Optional : boost::totally_ordered<Optional<Value>, ...@@ -110,7 +108,7 @@ class Optional : boost::totally_ordered<Optional<Value>,
} }
} }
/* implicit */ Optional(const None& empty) /* implicit */ Optional(const None&)
: hasValue_(false) { : hasValue_(false) {
} }
...@@ -179,32 +177,6 @@ class Optional : boost::totally_ordered<Optional<Value>, ...@@ -179,32 +177,6 @@ class Optional : boost::totally_ordered<Optional<Value>,
return *this; return *this;
} }
bool operator<(const Optional& other) const {
if (hasValue() != other.hasValue()) {
return hasValue() < other.hasValue();
}
if (hasValue()) {
return value() < other.value();
}
return false; // both empty
}
bool operator<(const Value& other) const {
return !hasValue() || value() < other;
}
bool operator==(const Optional& other) const {
if (hasValue()) {
return other.hasValue() && value() == other.value();
} else {
return !other.hasValue();
}
}
bool operator==(const Value& other) const {
return hasValue() && value() == other;
}
template<class... Args> template<class... Args>
void emplace(Args&&... args) { void emplace(Args&&... args) {
clear(); clear();
...@@ -230,9 +202,8 @@ class Optional : boost::totally_ordered<Optional<Value>, ...@@ -230,9 +202,8 @@ class Optional : boost::totally_ordered<Optional<Value>,
bool hasValue() const { return hasValue_; } bool hasValue() const { return hasValue_; }
/* safe bool idiom */ explicit operator bool() const {
operator bool_type() const { return hasValue();
return hasValue() ? &Optional::truthy : nullptr;
} }
const Value& operator*() const { return value(); } const Value& operator*() const { return value(); }
...@@ -286,6 +257,54 @@ Opt make_optional(T&& v) { ...@@ -286,6 +257,54 @@ Opt make_optional(T&& v) {
return Opt(std::forward<T>(v)); return Opt(std::forward<T>(v));
} }
template<class V>
bool operator< (const Optional<V>& a, const Optional<V>& b) {
if (a.hasValue() != b.hasValue()) { return a.hasValue() < b.hasValue(); }
if (a.hasValue()) { return a.value() < b.value(); }
return false;
}
template<class V>
bool operator==(const Optional<V>& a, const Optional<V>& b) {
if (a.hasValue() != b.hasValue()) { return false; }
if (a.hasValue()) { return a.value() == b.value(); }
return true;
}
template<class V>
bool operator<=(const Optional<V>& a, const Optional<V>& b) {
return !(b < a);
}
template<class V>
bool operator!=(const Optional<V>& a, const Optional<V>& b) {
return !(b == a);
}
template<class V>
bool operator>=(const Optional<V>& a, const Optional<V>& b) {
return !(a < b);
}
template<class V>
bool operator> (const Optional<V>& a, const Optional<V>& b) {
return b < a;
}
// To supress comparability of Optional<T> with T, despite implicit conversion.
template<class V> bool operator< (const Optional<V>&, const V& other) = delete;
template<class V> bool operator<=(const Optional<V>&, const V& other) = delete;
template<class V> bool operator==(const Optional<V>&, const V& other) = delete;
template<class V> bool operator!=(const Optional<V>&, const V& other) = delete;
template<class V> bool operator>=(const Optional<V>&, const V& other) = delete;
template<class V> bool operator> (const Optional<V>&, const V& other) = delete;
template<class V> bool operator< (const V& other, const Optional<V>&) = delete;
template<class V> bool operator<=(const V& other, const Optional<V>&) = delete;
template<class V> bool operator==(const V& other, const Optional<V>&) = delete;
template<class V> bool operator!=(const V& other, const Optional<V>&) = delete;
template<class V> bool operator>=(const V& other, const Optional<V>&) = delete;
template<class V> bool operator> (const V& other, const Optional<V>&) = delete;
} // namespace folly } // namespace folly
#endif//FOLLY_OPTIONAL_H_ #endif//FOLLY_OPTIONAL_H_
...@@ -26,10 +26,21 @@ ...@@ -26,10 +26,21 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <boost/optional.hpp> #include <boost/optional.hpp>
using namespace folly;
using std::unique_ptr; using std::unique_ptr;
using std::shared_ptr; using std::shared_ptr;
namespace folly {
template<class V>
std::ostream& operator<<(std::ostream& os, const Optional<V>& v) {
if (v) {
os << "Optional(" << v.value() << ')';
} else {
os << "None";
}
return os;
}
struct NoDefault { struct NoDefault {
NoDefault(int, int) {} NoDefault(int, int) {}
char a, b, c; char a, b, c;
...@@ -47,7 +58,7 @@ TEST(Optional, NoDefault) { ...@@ -47,7 +58,7 @@ TEST(Optional, NoDefault) {
Optional<NoDefault> x; Optional<NoDefault> x;
EXPECT_FALSE(x); EXPECT_FALSE(x);
x.emplace(4, 5); x.emplace(4, 5);
EXPECT_TRUE(x); EXPECT_TRUE(bool(x));
x.clear(); x.clear();
EXPECT_FALSE(x); EXPECT_FALSE(x);
} }
...@@ -56,62 +67,62 @@ TEST(Optional, String) { ...@@ -56,62 +67,62 @@ TEST(Optional, String) {
Optional<std::string> maybeString; Optional<std::string> maybeString;
EXPECT_FALSE(maybeString); EXPECT_FALSE(maybeString);
maybeString = "hello"; maybeString = "hello";
EXPECT_TRUE(maybeString); EXPECT_TRUE(bool(maybeString));
} }
TEST(Optional, Const) { TEST(Optional, Const) {
{ // default construct { // default construct
Optional<const int> opt; Optional<const int> opt;
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
opt.emplace(4); opt.emplace(4);
EXPECT_EQ(opt, 4); EXPECT_EQ(*opt, 4);
opt.emplace(5); opt.emplace(5);
EXPECT_EQ(opt, 5); EXPECT_EQ(*opt, 5);
opt.clear(); opt.clear();
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
} }
{ // copy-constructed { // copy-constructed
const int x = 6; const int x = 6;
Optional<const int> opt(x); Optional<const int> opt(x);
EXPECT_EQ(opt, 6); EXPECT_EQ(*opt, 6);
} }
{ // move-constructed { // move-constructed
const int x = 7; const int x = 7;
Optional<const int> opt(std::move(x)); Optional<const int> opt(std::move(x));
EXPECT_EQ(opt, 7); EXPECT_EQ(*opt, 7);
} }
// no assignment allowed // no assignment allowed
} }
TEST(Optional, Simple) { TEST(Optional, Simple) {
Optional<int> opt; Optional<int> opt;
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
opt = 4; opt = 4;
EXPECT_TRUE(opt); EXPECT_TRUE(bool(opt));
EXPECT_EQ(4, *opt); EXPECT_EQ(4, *opt);
opt = 5; opt = 5;
EXPECT_EQ(5, *opt); EXPECT_EQ(5, *opt);
opt.clear(); opt.clear();
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
} }
TEST(Optional, EmptyConstruct) { TEST(Optional, EmptyConstruct) {
Optional<int> opt; Optional<int> opt;
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
Optional<int> test1(opt); Optional<int> test1(opt);
EXPECT_FALSE(test1); EXPECT_FALSE(bool(test1));
Optional<int> test2(std::move(opt)); Optional<int> test2(std::move(opt));
EXPECT_FALSE(test2); EXPECT_FALSE(bool(test2));
} }
TEST(Optional, Unique) { TEST(Optional, Unique) {
Optional<unique_ptr<int>> opt; Optional<unique_ptr<int>> opt;
opt.clear(); opt.clear();
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
// empty->emplaced // empty->emplaced
opt.emplace(new int(5)); opt.emplace(new int(5));
EXPECT_TRUE(opt); EXPECT_TRUE(bool(opt));
EXPECT_EQ(5, **opt); EXPECT_EQ(5, **opt);
opt.clear(); opt.clear();
...@@ -124,24 +135,24 @@ TEST(Optional, Unique) { ...@@ -124,24 +135,24 @@ TEST(Optional, Unique) {
// move it out by move construct // move it out by move construct
Optional<unique_ptr<int>> moved(std::move(opt)); Optional<unique_ptr<int>> moved(std::move(opt));
EXPECT_TRUE(moved); EXPECT_TRUE(bool(moved));
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
EXPECT_EQ(7, **moved); EXPECT_EQ(7, **moved);
EXPECT_TRUE(moved); EXPECT_TRUE(bool(moved));
opt = std::move(moved); // move it back by move assign opt = std::move(moved); // move it back by move assign
EXPECT_FALSE(moved); EXPECT_FALSE(bool(moved));
EXPECT_TRUE(opt); EXPECT_TRUE(bool(opt));
EXPECT_EQ(7, **opt); EXPECT_EQ(7, **opt);
} }
TEST(Optional, Shared) { TEST(Optional, Shared) {
shared_ptr<int> ptr; shared_ptr<int> ptr;
Optional<shared_ptr<int>> opt; Optional<shared_ptr<int>> opt;
EXPECT_FALSE(opt); EXPECT_FALSE(bool(opt));
// empty->emplaced // empty->emplaced
opt.emplace(new int(5)); opt.emplace(new int(5));
EXPECT_TRUE(opt); EXPECT_TRUE(bool(opt));
ptr = opt.value(); ptr = opt.value();
EXPECT_EQ(ptr.get(), opt->get()); EXPECT_EQ(ptr.get(), opt->get());
EXPECT_EQ(2, ptr.use_count()); EXPECT_EQ(2, ptr.use_count());
...@@ -185,7 +196,7 @@ TEST(Optional, Order) { ...@@ -185,7 +196,7 @@ TEST(Optional, Order) {
{ 3 }, { 3 },
}; };
std::sort(vect.begin(), vect.end()); std::sort(vect.begin(), vect.end());
EXPECT_TRUE(vect == expected); EXPECT_EQ(vect, expected);
} }
TEST(Optional, Swap) { TEST(Optional, Swap) {
...@@ -218,14 +229,9 @@ TEST(Optional, Comparisons) { ...@@ -218,14 +229,9 @@ TEST(Optional, Comparisons) {
Optional<int> o1(1); Optional<int> o1(1);
Optional<int> o2(2); Optional<int> o2(2);
EXPECT_TRUE(o_ < 1);
EXPECT_TRUE(o_ <= 1);
EXPECT_TRUE(o_ <= o_); EXPECT_TRUE(o_ <= o_);
EXPECT_TRUE(o_ == o_); EXPECT_TRUE(o_ == o_);
EXPECT_TRUE(o_ != 1);
EXPECT_TRUE(o_ >= o_); EXPECT_TRUE(o_ >= o_);
EXPECT_TRUE(1 >= o_);
EXPECT_TRUE(1 > o_);
EXPECT_TRUE(o1 < o2); EXPECT_TRUE(o1 < o2);
EXPECT_TRUE(o1 <= o2); EXPECT_TRUE(o1 <= o2);
...@@ -245,6 +251,7 @@ TEST(Optional, Comparisons) { ...@@ -245,6 +251,7 @@ TEST(Optional, Comparisons) {
EXPECT_FALSE(o1 >= o2); EXPECT_FALSE(o1 >= o2);
EXPECT_FALSE(o1 > o2); EXPECT_FALSE(o1 > o2);
/* folly::Optional explicitly doesn't support comparisons with contained value
EXPECT_TRUE(1 < o2); EXPECT_TRUE(1 < o2);
EXPECT_TRUE(1 <= o2); EXPECT_TRUE(1 <= o2);
EXPECT_TRUE(1 <= o1); EXPECT_TRUE(1 <= o1);
...@@ -262,6 +269,68 @@ TEST(Optional, Comparisons) { ...@@ -262,6 +269,68 @@ TEST(Optional, Comparisons) {
EXPECT_FALSE(o1 >= 2); EXPECT_FALSE(o1 >= 2);
EXPECT_FALSE(o1 >= 2); EXPECT_FALSE(o1 >= 2);
EXPECT_FALSE(o1 > 2); EXPECT_FALSE(o1 > 2);
*/
// boost::optional does support comparison with contained value, which can
// lead to confusion when a bool is contained
boost::optional<int> boi(3);
EXPECT_TRUE(boi < 5);
EXPECT_TRUE(boi <= 4);
EXPECT_TRUE(boi == 3);
EXPECT_TRUE(boi != 2);
EXPECT_TRUE(boi >= 1);
EXPECT_TRUE(boi > 0);
EXPECT_TRUE(1 < boi);
EXPECT_TRUE(2 <= boi);
EXPECT_TRUE(3 == boi);
EXPECT_TRUE(4 != boi);
EXPECT_TRUE(5 >= boi);
EXPECT_TRUE(6 > boi);
boost::optional<bool> bob(false);
EXPECT_TRUE(bob);
EXPECT_TRUE(bob == false); // well that was confusing
EXPECT_FALSE(bob != false);
}
TEST(Optional, Conversions) {
Optional<bool> mbool;
Optional<short> mshort;
Optional<char*> mstr;
Optional<int> mint;
//These don't compile
//bool b = mbool;
//short s = mshort;
//char* c = mstr;
//int x = mint;
//char* c(mstr);
//short s(mshort);
//int x(mint);
// intended explicit operator bool, for if (opt).
bool b(mbool);
// Truthy tests work and are not ambiguous
if (mbool && mshort && mstr && mint) { // only checks not-empty
if (*mbool && *mshort && *mstr && *mint) { // only checks value
;
}
}
mbool = false;
EXPECT_TRUE(bool(mbool));
EXPECT_FALSE(*mbool);
mbool = true;
EXPECT_TRUE(bool(mbool));
EXPECT_TRUE(*mbool);
mbool = none;
EXPECT_FALSE(bool(mbool));
// No conversion allowed; does not compile
// EXPECT_TRUE(mbool == false);
} }
TEST(Optional, Pointee) { TEST(Optional, Pointee) {
...@@ -270,7 +339,7 @@ TEST(Optional, Pointee) { ...@@ -270,7 +339,7 @@ TEST(Optional, Pointee) {
x = 1; x = 1;
EXPECT_TRUE(get_pointer(x)); EXPECT_TRUE(get_pointer(x));
*get_pointer(x) = 2; *get_pointer(x) = 2;
EXPECT_TRUE(x == 2); EXPECT_TRUE(*x == 2);
x = none; x = none;
EXPECT_FALSE(get_pointer(x)); EXPECT_FALSE(get_pointer(x));
} }
...@@ -353,3 +422,5 @@ TEST(Optional, AssignmentContained) { ...@@ -353,3 +422,5 @@ TEST(Optional, AssignmentContained) {
EXPECT_FALSE(target.hasValue()); EXPECT_FALSE(target.hasValue());
} }
} }
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment