From: Tom Jackson Date: Mon, 22 Apr 2013 18:07:07 +0000 (-0700) Subject: Disabling conversion with contained value for Optional X-Git-Tag: v0.22.0~994 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=5f517beb049f122c268c3b5bf2eb747b5e7b3a7b;p=folly.git 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 --- diff --git a/folly/Optional.h b/folly/Optional.h index 871357ce..4295e271 100644 --- a/folly/Optional.h +++ b/folly/Optional.h @@ -61,6 +61,7 @@ #include + namespace folly { namespace detail { struct NoneHelper {}; } @@ -81,10 +82,7 @@ const None none = nullptr; #endif // __GNUC__ template -class Optional : boost::totally_ordered, - boost::totally_ordered, Value>> { - typedef void (Optional::*bool_type)() const; - void truthy() const {}; +class Optional { public: static_assert(!std::is_reference::value, "Optional may not be used with reference types"); @@ -110,7 +108,7 @@ class Optional : boost::totally_ordered, } } - /* implicit */ Optional(const None& empty) + /* implicit */ Optional(const None&) : hasValue_(false) { } @@ -179,32 +177,6 @@ class Optional : boost::totally_ordered, 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 void emplace(Args&&... args) { clear(); @@ -230,9 +202,8 @@ class Optional : boost::totally_ordered, bool hasValue() const { return hasValue_; } - /* safe bool idiom */ - operator bool_type() const { - return hasValue() ? &Optional::truthy : nullptr; + explicit operator bool() const { + return hasValue(); } const Value& operator*() const { return value(); } @@ -286,6 +257,54 @@ Opt make_optional(T&& v) { return Opt(std::forward(v)); } +template +bool operator< (const Optional& a, const Optional& b) { + if (a.hasValue() != b.hasValue()) { return a.hasValue() < b.hasValue(); } + if (a.hasValue()) { return a.value() < b.value(); } + return false; +} + +template +bool operator==(const Optional& a, const Optional& b) { + if (a.hasValue() != b.hasValue()) { return false; } + if (a.hasValue()) { return a.value() == b.value(); } + return true; +} + +template +bool operator<=(const Optional& a, const Optional& b) { + return !(b < a); +} + +template +bool operator!=(const Optional& a, const Optional& b) { + return !(b == a); +} + +template +bool operator>=(const Optional& a, const Optional& b) { + return !(a < b); +} + +template +bool operator> (const Optional& a, const Optional& b) { + return b < a; +} + +// To supress comparability of Optional with T, despite implicit conversion. +template bool operator< (const Optional&, const V& other) = delete; +template bool operator<=(const Optional&, const V& other) = delete; +template bool operator==(const Optional&, const V& other) = delete; +template bool operator!=(const Optional&, const V& other) = delete; +template bool operator>=(const Optional&, const V& other) = delete; +template bool operator> (const Optional&, const V& other) = delete; +template bool operator< (const V& other, const Optional&) = delete; +template bool operator<=(const V& other, const Optional&) = delete; +template bool operator==(const V& other, const Optional&) = delete; +template bool operator!=(const V& other, const Optional&) = delete; +template bool operator>=(const V& other, const Optional&) = delete; +template bool operator> (const V& other, const Optional&) = delete; + } // namespace folly #endif//FOLLY_OPTIONAL_H_ diff --git a/folly/test/OptionalTest.cpp b/folly/test/OptionalTest.cpp index 68e81649..0e035fda 100644 --- a/folly/test/OptionalTest.cpp +++ b/folly/test/OptionalTest.cpp @@ -26,10 +26,21 @@ #include #include -using namespace folly; using std::unique_ptr; using std::shared_ptr; +namespace folly { + +template +std::ostream& operator<<(std::ostream& os, const Optional& v) { + if (v) { + os << "Optional(" << v.value() << ')'; + } else { + os << "None"; + } + return os; +} + struct NoDefault { NoDefault(int, int) {} char a, b, c; @@ -47,7 +58,7 @@ TEST(Optional, NoDefault) { Optional x; EXPECT_FALSE(x); x.emplace(4, 5); - EXPECT_TRUE(x); + EXPECT_TRUE(bool(x)); x.clear(); EXPECT_FALSE(x); } @@ -56,62 +67,62 @@ TEST(Optional, String) { Optional maybeString; EXPECT_FALSE(maybeString); maybeString = "hello"; - EXPECT_TRUE(maybeString); + EXPECT_TRUE(bool(maybeString)); } TEST(Optional, Const) { { // default construct Optional opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); opt.emplace(4); - EXPECT_EQ(opt, 4); + EXPECT_EQ(*opt, 4); opt.emplace(5); - EXPECT_EQ(opt, 5); + EXPECT_EQ(*opt, 5); opt.clear(); - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); } { // copy-constructed const int x = 6; Optional opt(x); - EXPECT_EQ(opt, 6); + EXPECT_EQ(*opt, 6); } { // move-constructed const int x = 7; Optional opt(std::move(x)); - EXPECT_EQ(opt, 7); + EXPECT_EQ(*opt, 7); } // no assignment allowed } TEST(Optional, Simple) { Optional opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); opt = 4; - EXPECT_TRUE(opt); + EXPECT_TRUE(bool(opt)); EXPECT_EQ(4, *opt); opt = 5; EXPECT_EQ(5, *opt); opt.clear(); - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); } TEST(Optional, EmptyConstruct) { Optional opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); Optional test1(opt); - EXPECT_FALSE(test1); + EXPECT_FALSE(bool(test1)); Optional test2(std::move(opt)); - EXPECT_FALSE(test2); + EXPECT_FALSE(bool(test2)); } TEST(Optional, Unique) { Optional> opt; opt.clear(); - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); // empty->emplaced opt.emplace(new int(5)); - EXPECT_TRUE(opt); + EXPECT_TRUE(bool(opt)); EXPECT_EQ(5, **opt); opt.clear(); @@ -124,24 +135,24 @@ TEST(Optional, Unique) { // move it out by move construct Optional> moved(std::move(opt)); - EXPECT_TRUE(moved); - EXPECT_FALSE(opt); + EXPECT_TRUE(bool(moved)); + EXPECT_FALSE(bool(opt)); EXPECT_EQ(7, **moved); - EXPECT_TRUE(moved); + EXPECT_TRUE(bool(moved)); opt = std::move(moved); // move it back by move assign - EXPECT_FALSE(moved); - EXPECT_TRUE(opt); + EXPECT_FALSE(bool(moved)); + EXPECT_TRUE(bool(opt)); EXPECT_EQ(7, **opt); } TEST(Optional, Shared) { shared_ptr ptr; Optional> opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); // empty->emplaced opt.emplace(new int(5)); - EXPECT_TRUE(opt); + EXPECT_TRUE(bool(opt)); ptr = opt.value(); EXPECT_EQ(ptr.get(), opt->get()); EXPECT_EQ(2, ptr.use_count()); @@ -185,7 +196,7 @@ TEST(Optional, Order) { { 3 }, }; std::sort(vect.begin(), vect.end()); - EXPECT_TRUE(vect == expected); + EXPECT_EQ(vect, expected); } TEST(Optional, Swap) { @@ -218,14 +229,9 @@ TEST(Optional, Comparisons) { Optional o1(1); Optional o2(2); - EXPECT_TRUE(o_ < 1); - EXPECT_TRUE(o_ <= 1); EXPECT_TRUE(o_ <= o_); EXPECT_TRUE(o_ == o_); - EXPECT_TRUE(o_ != 1); EXPECT_TRUE(o_ >= o_); - EXPECT_TRUE(1 >= o_); - EXPECT_TRUE(1 > o_); EXPECT_TRUE(o1 < o2); EXPECT_TRUE(o1 <= o2); @@ -245,6 +251,7 @@ TEST(Optional, Comparisons) { 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 <= o1); @@ -262,6 +269,68 @@ TEST(Optional, Comparisons) { 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 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 bob(false); + EXPECT_TRUE(bob); + EXPECT_TRUE(bob == false); // well that was confusing + EXPECT_FALSE(bob != false); +} + +TEST(Optional, Conversions) { + Optional mbool; + Optional mshort; + Optional mstr; + Optional 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) { @@ -270,7 +339,7 @@ TEST(Optional, Pointee) { x = 1; EXPECT_TRUE(get_pointer(x)); *get_pointer(x) = 2; - EXPECT_TRUE(x == 2); + EXPECT_TRUE(*x == 2); x = none; EXPECT_FALSE(get_pointer(x)); } @@ -353,3 +422,5 @@ TEST(Optional, AssignmentContained) { EXPECT_FALSE(target.hasValue()); } } + +}