From: Tom Jackson Date: Sat, 22 Sep 2012 00:19:01 +0000 (-0700) Subject: Optional X-Git-Tag: v0.22.0~1168 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=a8991a6b6fa1cc08adc57f300376aeccc279cee6 Optional Summary: Optional, like boost::optional, but with full rvalue support. Test Plan: Unit tests Reviewed By: delong.j@fb.com FB internal diff: D571810 --- diff --git a/folly/Optional.h b/folly/Optional.h new file mode 100644 index 00000000..311f1642 --- /dev/null +++ b/folly/Optional.h @@ -0,0 +1,261 @@ +/* + * Copyright 2012 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOLLY_OPTIONAL_H_ +#define FOLLY_OPTIONAL_H_ + +/* + * Optional - For conditional initialization of values, like boost::optional, + * but with support for move semantics and emplacement. Reference type support + * has not been included due to limited use cases and potential confusion with + * semantics of assignment: Assigning to an optional reference could quite + * reasonably copy its value or redirect the reference. + * + * Optional can be useful when a variable might or might not be needed: + * + * Optional maybeLogger = ...; + * if (maybeLogger) { + * maybeLogger->log("hello"); + * } + * + * Optional enables a 'null' value for types which do not otherwise have + * nullability, especially useful for parameter passing: + * + * void testIterator(const unique_ptr& it, + * initializer_list idsExpected, + * Optional> ranksExpected = none) { + * for (int i = 0; it->next(); ++i) { + * EXPECT_EQ(it->doc().id(), idsExpected[i]); + * if (ranksExpected) { + * EXPECT_EQ(it->doc().rank(), (*ranksExpected)[i]); + * } + * } + * } + * + * Optional models OptionalPointee, so calling 'get_pointer(opt)' will return a + * pointer to nullptr if the 'opt' is empty, and a pointer to the value if it is + * not: + * + * Optional maybeInt = ...; + * if (int* v = get_pointer(maybeInt)) { + * cout << *v << endl; + * } + */ +#include +#include +#include +#include + +#include + +namespace folly { + +namespace detail { struct NoneHelper {}; } + +typedef int detail::NoneHelper::*None; + +const None none = nullptr; + +/** + * gcc-4.7 warns about use of uninitialized memory around the use of storage_ + * even though this is explicitly initialized at each point. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" + +template +class Optional : boost::totally_ordered, + boost::totally_ordered, Value>> { + typedef void (Optional::*bool_type)() const; + void truthy() const {}; + public: + static_assert(!std::is_reference::value, + "Optional may not be used with reference types"); + + Optional() + : hasValue_(false) { + } + + Optional(const Optional& src) { + construct(src.value()); + } + + Optional(Optional&& src) { + construct(std::move(src.value())); + src.clear(); + } + + /* implicit */ Optional(const None& empty) + : hasValue_(false) { + } + + /* implicit */ Optional(Value&& newValue) { + construct(std::move(newValue)); + } + + /* implicit */ Optional(const Value& newValue) { + construct(newValue); + } + + ~Optional() { + clear(); + } + + void assign(const None&) { + clear(); + } + + void assign(Optional&& src) { + if (src.hasValue()) { + assign(std::move(src.value())); + src.clear(); + } else { + clear(); + } + } + + void assign(const Optional& src) { + if (src.hasValue()) { + assign(src.value()); + } else { + clear(); + } + } + + void assign(Value&& newValue) { + if (hasValue()) { + value_ = std::move(newValue); + } else { + construct(std::move(newValue)); + } + } + + void assign(const Value& newValue) { + if (hasValue()) { + value_ = newValue; + } else { + construct(newValue); + } + } + + template + Optional& operator=(Arg&& arg) { + assign(std::forward(arg)); + 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(); + construct(std::forward(args)...); + } + + void clear() { + if (hasValue()) { + hasValue_ = false; + value().~Value(); + } + } + + const Value& value() const { + assert(hasValue()); + return value_; + } + + Value& value() { + assert(hasValue()); + return value_; + } + + bool hasValue() const { return hasValue_; } + + /* safe bool idiom */ + operator bool_type() const { + return hasValue() ? &Optional::truthy : nullptr; + } + + const Value& operator*() const { return value(); } + Value& operator*() { return value(); } + + const Value* operator->() const { return &value(); } + Value* operator->() { return &value(); } + + private: + template + void construct(Args&&... args) { + const void* ptr = &value_; + // for supporting const types + new(const_cast(ptr)) Value(std::forward(args)...); + hasValue_ = true; + } + + // uninitialized + union { Value value_; }; + bool hasValue_; +}; + +#pragma GCC diagnostic pop + +template +const T* get_pointer(const Optional& opt) { + return opt ? &opt.value() : nullptr; +} + +template +T* get_pointer(Optional& opt) { + return opt ? &opt.value() : nullptr; +} + +template +void swap(Optional& a, Optional& b) { + if (a.hasValue() && b.hasValue()) { + // both full + using std::swap; + swap(a.value(), b.value()); + } else if (a.hasValue() || b.hasValue()) { + std::swap(a, b); // fall back to default implementation if they're mixed. + } +} + +}// namespace folly + +#endif//FOLLY_OPTIONAL_H_ diff --git a/folly/test/OptionalTest.cpp b/folly/test/OptionalTest.cpp new file mode 100644 index 00000000..1268ff66 --- /dev/null +++ b/folly/test/OptionalTest.cpp @@ -0,0 +1,267 @@ +/* + * Copyright 2012 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "folly/Optional.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace folly; +using std::unique_ptr; +using std::shared_ptr; + +struct NoDefault { + NoDefault(int, int) {} + char a, b, c; +}; + +static_assert(sizeof(Optional) == 2, ""); +static_assert(sizeof(Optional) == 8, ""); +static_assert(sizeof(Optional) == 4, ""); +static_assert(sizeof(Optional) == sizeof(boost::optional), ""); +static_assert(sizeof(Optional) == sizeof(boost::optional), ""); +static_assert(sizeof(Optional) == sizeof(boost::optional), ""); +static_assert(sizeof(Optional) == sizeof(boost::optional), ""); + +TEST(Optional, NoDefault) { + Optional x; + EXPECT_FALSE(x); + x.emplace(4, 5); + EXPECT_TRUE(x); + x.clear(); + EXPECT_FALSE(x); +} + +TEST(Optional, String) { + Optional maybeString; + EXPECT_FALSE(maybeString); + maybeString = "hello"; + EXPECT_TRUE(maybeString); +} + +TEST(Optional, Const) { + { // default construct + Optional opt; + EXPECT_FALSE(opt); + opt.emplace(4); + EXPECT_EQ(opt, 4); + opt.emplace(5); + EXPECT_EQ(opt, 5); + opt.clear(); + EXPECT_FALSE(opt); + } + { // copy-constructed + const int x = 6; + Optional opt(x); + EXPECT_EQ(opt, 6); + } + { // move-constructed + const int x = 7; + Optional opt(std::move(x)); + EXPECT_EQ(opt, 7); + } + // no assignment allowed +} + +TEST(Optional, Simple) { + Optional opt; + EXPECT_FALSE(opt); + opt = 4; + EXPECT_TRUE(opt); + EXPECT_EQ(4, *opt); + opt = 5; + EXPECT_EQ(5, *opt); + opt.clear(); + EXPECT_FALSE(opt); +} + +TEST(Optional, Unique) { + Optional> opt; + + opt.clear(); + EXPECT_FALSE(opt); + // empty->emplaced + opt.emplace(new int(5)); + EXPECT_TRUE(opt); + EXPECT_EQ(5, **opt); + + opt.clear(); + // empty->moved + opt = unique_ptr(new int(6)); + EXPECT_EQ(6, **opt); + // full->moved + opt = unique_ptr(new int(7)); + EXPECT_EQ(7, **opt); + + // move it out by move construct + Optional> moved(std::move(opt)); + EXPECT_TRUE(moved); + EXPECT_FALSE(opt); + EXPECT_EQ(7, **moved); + + EXPECT_TRUE(moved); + opt = std::move(moved); // move it back by move assign + EXPECT_FALSE(moved); + EXPECT_TRUE(opt); + EXPECT_EQ(7, **opt); +} + +TEST(Optional, Shared) { + shared_ptr ptr; + Optional> opt; + EXPECT_FALSE(opt); + // empty->emplaced + opt.emplace(new int(5)); + EXPECT_TRUE(opt); + ptr = opt.value(); + EXPECT_EQ(ptr.get(), opt->get()); + EXPECT_EQ(2, ptr.use_count()); + opt.clear(); + EXPECT_EQ(1, ptr.use_count()); + // full->copied + opt = ptr; + EXPECT_EQ(2, ptr.use_count()); + EXPECT_EQ(ptr.get(), opt->get()); + opt.clear(); + EXPECT_EQ(1, ptr.use_count()); + // full->moved + opt = std::move(ptr); + EXPECT_EQ(1, opt->use_count()); + EXPECT_EQ(nullptr, ptr.get()); + { + Optional> copied(opt); + EXPECT_EQ(2, opt->use_count()); + Optional> moved(std::move(opt)); + EXPECT_EQ(2, moved->use_count()); + moved.emplace(new int(6)); + EXPECT_EQ(1, moved->use_count()); + copied = moved; + EXPECT_EQ(2, moved->use_count()); + } +} + +TEST(Optional, Order) { + std::vector> vect{ + { none }, + { 3 }, + { 1 }, + { none }, + { 2 }, + }; + std::vector> expected { + { none }, + { none }, + { 1 }, + { 2 }, + { 3 }, + }; + std::sort(vect.begin(), vect.end()); + EXPECT_TRUE(vect == expected); +} + +TEST(Optional, Swap) { + Optional a; + Optional b; + + swap(a, b); + EXPECT_FALSE(a.hasValue()); + EXPECT_FALSE(b.hasValue()); + + a = "hello"; + EXPECT_TRUE(a.hasValue()); + EXPECT_FALSE(b.hasValue()); + EXPECT_EQ("hello", a.value()); + + swap(a, b); + EXPECT_FALSE(a.hasValue()); + EXPECT_TRUE(b.hasValue()); + EXPECT_EQ("hello", b.value()); + + a = "bye"; + EXPECT_TRUE(a.hasValue()); + EXPECT_EQ("bye", a.value()); + + swap(a, b); +} + +TEST(Optional, Comparisons) { + Optional o_; + 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); + EXPECT_TRUE(o1 <= o1); + EXPECT_TRUE(o1 == o1); + EXPECT_TRUE(o1 != o2); + EXPECT_TRUE(o1 >= o1); + EXPECT_TRUE(o2 >= o1); + EXPECT_TRUE(o2 > o1); + + EXPECT_FALSE(o2 < o1); + EXPECT_FALSE(o2 <= o1); + EXPECT_FALSE(o2 <= o1); + EXPECT_FALSE(o2 == o1); + EXPECT_FALSE(o1 != o1); + EXPECT_FALSE(o1 >= o2); + EXPECT_FALSE(o1 >= o2); + EXPECT_FALSE(o1 > o2); + + EXPECT_TRUE(1 < o2); + EXPECT_TRUE(1 <= o2); + EXPECT_TRUE(1 <= o1); + EXPECT_TRUE(1 == o1); + EXPECT_TRUE(2 != o1); + EXPECT_TRUE(1 >= o1); + EXPECT_TRUE(2 >= o1); + EXPECT_TRUE(2 > o1); + + EXPECT_FALSE(o2 < 1); + EXPECT_FALSE(o2 <= 1); + EXPECT_FALSE(o2 <= 1); + EXPECT_FALSE(o2 == 1); + EXPECT_FALSE(o2 != 2); + EXPECT_FALSE(o1 >= 2); + EXPECT_FALSE(o1 >= 2); + EXPECT_FALSE(o1 > 2); +} + +TEST(Optional, Pointee) { + Optional x; + EXPECT_FALSE(get_pointer(x)); + x = 1; + EXPECT_TRUE(get_pointer(x)); + *get_pointer(x) = 2; + EXPECT_TRUE(x == 2); + x = none; + EXPECT_FALSE(get_pointer(x)); +}