/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::shared_ptr; using std::unique_ptr; namespace { struct HashableStruct {}; struct UnhashableStruct {}; } // namespace namespace std { template <> struct hash { [[maybe_unused]] size_t operator()(const HashableStruct&) const noexcept { return 0; } }; } // namespace std namespace folly { namespace { 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; }; } // namespace static_assert(sizeof(Optional) == 2, ""); static_assert(sizeof(Optional) == 8, ""); static_assert(sizeof(Optional) == 4, ""); static_assert(sizeof(Optional) == sizeof(std::optional), ""); static_assert(sizeof(Optional) == sizeof(std::optional), ""); static_assert(sizeof(Optional) == sizeof(std::optional), ""); static_assert(sizeof(Optional) == sizeof(std::optional), ""); static_assert(is_hashable_v>); static_assert(!is_hashable_v>); TEST(Optional, ConstexprConstructible) { // Use FOLLY_STORAGE_CONSTEXPR to work around MSVC not taking this. static FOLLY_STORAGE_CONSTEXPR Optional opt; // NOTE: writing `opt = none` instead of `opt(none)` causes gcc to reject this // code, claiming that the (non-constexpr) move ctor of `Optional` is being // invoked. static FOLLY_STORAGE_CONSTEXPR Optional opt2(none); EXPECT_FALSE(opt.has_value()); EXPECT_FALSE(opt2.has_value()); } TEST(Optional, NoDefault) { Optional x; EXPECT_FALSE(x); x.emplace(4, 5); EXPECT_TRUE(bool(x)); x.reset(); EXPECT_FALSE(x); } TEST(Optional, Emplace) { Optional> opt; auto& values1 = opt.emplace(3, 4); EXPECT_THAT(values1, testing::ElementsAre(4, 4, 4)); auto& values2 = opt.emplace(2, 5); EXPECT_THAT(values2, testing::ElementsAre(5, 5)); } TEST(Optional, EmplaceInitializerList) { Optional> opt; auto& values1 = opt.emplace({3, 4, 5}); EXPECT_THAT(values1, testing::ElementsAre(3, 4, 5)); auto& values2 = opt.emplace({4, 5, 6}); EXPECT_THAT(values2, testing::ElementsAre(4, 5, 6)); } TEST(Optional, Reset) { Optional opt(3); opt.reset(); EXPECT_FALSE(opt); } TEST(Optional, String) { Optional maybeString; EXPECT_FALSE(maybeString); maybeString = "hello"; EXPECT_TRUE(bool(maybeString)); } TEST(Optional, Const) { { // default construct Optional opt; EXPECT_FALSE(bool(opt)); opt.emplace(4); EXPECT_EQ(*opt, 4); opt.emplace(5); EXPECT_EQ(*opt, 5); opt.reset(); EXPECT_FALSE(bool(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(bool(opt)); EXPECT_EQ(42, opt.value_or(42)); opt = 4; EXPECT_TRUE(bool(opt)); EXPECT_EQ(4, *opt); EXPECT_EQ(4, opt.value_or(42)); opt = 5; EXPECT_EQ(5, *opt); opt.reset(); EXPECT_FALSE(bool(opt)); } namespace { class MoveTester { public: /* implicit */ MoveTester(const char* s) : s_(s) {} MoveTester(const MoveTester&) = default; MoveTester(MoveTester&& other) noexcept { s_ = std::move(other.s_); other.s_ = ""; } MoveTester& operator=(const MoveTester&) = default; MoveTester& operator=(MoveTester&& other) noexcept { s_ = std::move(other.s_); other.s_ = ""; return *this; } private: friend bool operator==(const MoveTester& o1, const MoveTester& o2); std::string s_; }; bool operator==(const MoveTester& o1, const MoveTester& o2) { return o1.s_ == o2.s_; } } // namespace TEST(Optional, valueOrRvalueArg) { Optional opt; MoveTester dflt = "hello"; EXPECT_EQ("hello", opt.value_or(dflt)); EXPECT_EQ("hello", dflt); EXPECT_EQ("hello", opt.value_or(std::move(dflt))); EXPECT_EQ("", dflt); EXPECT_EQ("world", opt.value_or("world")); dflt = "hello"; // Make sure that the const overload works on const objects const auto& optc = opt; EXPECT_EQ("hello", optc.value_or(dflt)); EXPECT_EQ("hello", dflt); EXPECT_EQ("hello", optc.value_or(std::move(dflt))); EXPECT_EQ("", dflt); EXPECT_EQ("world", optc.value_or("world")); dflt = "hello"; opt = "meow"; EXPECT_EQ("meow", opt.value_or(dflt)); EXPECT_EQ("hello", dflt); EXPECT_EQ("meow", opt.value_or(std::move(dflt))); EXPECT_EQ("hello", dflt); // only moved if used } TEST(Optional, valueOrNoncopyable) { Optional> opt; std::unique_ptr dflt(new int(42)); EXPECT_EQ(42, *std::move(opt).value_or(std::move(dflt))); } struct ExpectingDeleter { explicit ExpectingDeleter(int expected_) : expected(expected_) {} int expected; void operator()(const int* ptr) { EXPECT_EQ(*ptr, expected); delete ptr; } }; TEST(Optional, valueMove) { auto ptr = Optional>( {new int(42), ExpectingDeleter{1337}}) .value(); *ptr = 1337; } TEST(Optional, dereferenceMove) { auto ptr = *Optional>( {new int(42), ExpectingDeleter{1337}}); *ptr = 1337; } TEST(Optional, EmptyConstruct) { Optional opt; EXPECT_FALSE(bool(opt)); Optional test1(opt); EXPECT_FALSE(bool(test1)); Optional test2(std::move(opt)); EXPECT_FALSE(bool(test2)); } TEST(Optional, InPlaceConstruct) { using A = std::pair; Optional opt(std::in_place, 5, 3.2); EXPECT_TRUE(bool(opt)); EXPECT_EQ(5, opt->first); } TEST(Optional, InPlaceNestedConstruct) { using A = std::pair; Optional> opt(std::in_place, std::in_place, 5, 3.2); EXPECT_TRUE(bool(opt)); EXPECT_TRUE(bool(*opt)); EXPECT_EQ(5, (*opt)->first); } TEST(Optional, Unique) { Optional> opt; opt.reset(); EXPECT_FALSE(bool(opt)); // empty->emplaced opt.emplace(new int(5)); EXPECT_TRUE(bool(opt)); EXPECT_EQ(5, **opt); opt.reset(); // empty->moved opt = std::make_unique(6); EXPECT_EQ(6, **opt); // full->moved opt = std::make_unique(7); EXPECT_EQ(7, **opt); // move it out by move construct Optional> moved(std::move(opt)); EXPECT_TRUE(bool(moved)); EXPECT_FALSE(bool(opt)); EXPECT_EQ(7, **moved); EXPECT_TRUE(bool(moved)); opt = std::move(moved); // move it back by move assign EXPECT_FALSE(bool(moved)); EXPECT_TRUE(bool(opt)); EXPECT_EQ(7, **opt); } TEST(Optional, Shared) { shared_ptr ptr; Optional> opt; EXPECT_FALSE(bool(opt)); // empty->emplaced opt.emplace(new int(5)); EXPECT_TRUE(bool(opt)); ptr = opt.value(); EXPECT_EQ(ptr.get(), opt->get()); EXPECT_EQ(2, ptr.use_count()); opt.reset(); EXPECT_EQ(1, ptr.use_count()); // full->copied opt = ptr; EXPECT_EQ(2, ptr.use_count()); EXPECT_EQ(ptr.get(), opt->get()); opt.reset(); 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_EQ(vect, expected); } TEST(Optional, Swap) { Optional a; Optional b; swap(a, b); EXPECT_FALSE(a.has_value()); EXPECT_FALSE(b.has_value()); a = "hello"; EXPECT_TRUE(a.has_value()); EXPECT_FALSE(b.has_value()); EXPECT_EQ("hello", a.value()); swap(a, b); EXPECT_FALSE(a.has_value()); EXPECT_TRUE(b.has_value()); EXPECT_EQ("hello", b.value()); a = "bye"; EXPECT_TRUE(a.has_value()); EXPECT_EQ("bye", a.value()); swap(a, b); EXPECT_TRUE(a.has_value()); EXPECT_TRUE(b.has_value()); EXPECT_EQ("hello", a.value()); EXPECT_EQ("bye", b.value()); } TEST(Optional, Comparisons) { Optional o_; Optional o1(1); Optional o2(2); EXPECT_TRUE(o_ <= (o_)); EXPECT_TRUE(o_ == (o_)); EXPECT_TRUE(o_ >= (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); /* folly::Optional explicitly doesn't support comparisons with contained value 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); */ // std::optional does support comparison with contained value, which can // lead to confusion when a bool is contained std::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); std::optional bob(false); EXPECT_TRUE((bool)bob); EXPECT_TRUE(bob == false); // well that was confusing EXPECT_FALSE(bob != false); } TEST(Optional, HeterogeneousComparisons) { using opt8 = Optional; using opt64 = Optional; EXPECT_TRUE(opt8(4) == uint64_t(4)); EXPECT_FALSE(opt8(8) == uint64_t(4)); EXPECT_FALSE(opt8() == uint64_t(4)); EXPECT_TRUE(uint64_t(4) == opt8(4)); EXPECT_FALSE(uint64_t(4) == opt8(8)); EXPECT_FALSE(uint64_t(4) == opt8()); EXPECT_FALSE(opt8(4) != uint64_t(4)); EXPECT_TRUE(opt8(8) != uint64_t(4)); EXPECT_TRUE(opt8() != uint64_t(4)); EXPECT_FALSE(uint64_t(4) != opt8(4)); EXPECT_TRUE(uint64_t(4) != opt8(8)); EXPECT_TRUE(uint64_t(4) != opt8()); EXPECT_TRUE(opt8() == opt64()); EXPECT_TRUE(opt8(4) == opt64(4)); EXPECT_FALSE(opt8(8) == opt64(4)); EXPECT_FALSE(opt8() == opt64(4)); EXPECT_FALSE(opt8(4) == opt64()); EXPECT_FALSE(opt8() != opt64()); EXPECT_FALSE(opt8(4) != opt64(4)); EXPECT_TRUE(opt8(8) != opt64(4)); EXPECT_TRUE(opt8() != opt64(4)); EXPECT_TRUE(opt8(4) != opt64()); EXPECT_TRUE(opt8() < opt64(4)); EXPECT_TRUE(opt8(4) < opt64(8)); EXPECT_FALSE(opt8() < opt64()); EXPECT_FALSE(opt8(4) < opt64(4)); EXPECT_FALSE(opt8(8) < opt64(4)); EXPECT_FALSE(opt8(4) < opt64()); EXPECT_FALSE(opt8() > opt64(4)); EXPECT_FALSE(opt8(4) > opt64(8)); EXPECT_FALSE(opt8() > opt64()); EXPECT_FALSE(opt8(4) > opt64(4)); EXPECT_TRUE(opt8(8) > opt64(4)); EXPECT_TRUE(opt8(4) > opt64()); EXPECT_TRUE(opt8() <= opt64(4)); EXPECT_TRUE(opt8(4) <= opt64(8)); EXPECT_TRUE(opt8() <= opt64()); EXPECT_TRUE(opt8(4) <= opt64(4)); EXPECT_FALSE(opt8(8) <= opt64(4)); EXPECT_FALSE(opt8(4) <= opt64()); EXPECT_FALSE(opt8() >= opt64(4)); EXPECT_FALSE(opt8(4) >= opt64(8)); EXPECT_TRUE(opt8() >= opt64()); EXPECT_TRUE(opt8(4) >= opt64(4)); EXPECT_TRUE(opt8(8) >= opt64(4)); EXPECT_TRUE(opt8(4) >= opt64()); } TEST(Optional, NoneComparisons) { using opt = Optional; EXPECT_TRUE(opt() == none); EXPECT_TRUE(none == opt()); EXPECT_FALSE(opt(1) == none); EXPECT_FALSE(none == opt(1)); EXPECT_FALSE(opt() != none); EXPECT_FALSE(none != opt()); EXPECT_TRUE(opt(1) != none); EXPECT_TRUE(none != opt(1)); EXPECT_FALSE(opt() < none); EXPECT_FALSE(none < opt()); EXPECT_FALSE(opt(1) < none); EXPECT_TRUE(none < opt(1)); EXPECT_FALSE(opt() > none); EXPECT_FALSE(none > opt()); EXPECT_FALSE(none > opt(1)); EXPECT_TRUE(opt(1) > none); EXPECT_TRUE(opt() <= none); EXPECT_TRUE(none <= opt()); EXPECT_FALSE(opt(1) <= none); EXPECT_TRUE(none <= opt(1)); EXPECT_TRUE(opt() >= none); EXPECT_TRUE(none >= opt()); EXPECT_TRUE(opt(1) >= none); EXPECT_FALSE(none >= opt(1)); } 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); EXPECT_FALSE(b); // 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) { 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)); } namespace { class ConstructibleWithArgsOnly { public: explicit ConstructibleWithArgsOnly(int, double) {} ConstructibleWithArgsOnly() = delete; ConstructibleWithArgsOnly(const ConstructibleWithArgsOnly&) = delete; ConstructibleWithArgsOnly(ConstructibleWithArgsOnly&&) = delete; ConstructibleWithArgsOnly& operator=(const ConstructibleWithArgsOnly&) = delete; ConstructibleWithArgsOnly& operator=(ConstructibleWithArgsOnly&&) = delete; }; class ConstructibleWithInitializerListAndArgsOnly { public: ConstructibleWithInitializerListAndArgsOnly( std::initializer_list, double) {} ConstructibleWithInitializerListAndArgsOnly() = delete; ConstructibleWithInitializerListAndArgsOnly( const ConstructibleWithInitializerListAndArgsOnly&) = delete; ConstructibleWithInitializerListAndArgsOnly( ConstructibleWithInitializerListAndArgsOnly&&) = delete; ConstructibleWithInitializerListAndArgsOnly& operator=( const ConstructibleWithInitializerListAndArgsOnly&) = delete; ConstructibleWithInitializerListAndArgsOnly& operator=( ConstructibleWithInitializerListAndArgsOnly&&) = delete; }; } // namespace TEST(Optional, MakeOptional) { // const L-value version const std::string s("abc"); auto optStr = folly::make_optional(s); ASSERT_TRUE(optStr.has_value()); EXPECT_EQ(*optStr, "abc"); *optStr = "cde"; EXPECT_EQ(s, "abc"); EXPECT_EQ(*optStr, "cde"); // L-value version std::string s2("abc"); auto optStr2 = folly::make_optional(s2); ASSERT_TRUE(optStr2.has_value()); EXPECT_EQ(*optStr2, "abc"); *optStr2 = "cde"; // it's vital to check that s2 wasn't clobbered EXPECT_EQ(s2, "abc"); // L-value reference version std::string& s3(s2); auto optStr3 = folly::make_optional(s3); ASSERT_TRUE(optStr3.has_value()); EXPECT_EQ(*optStr3, "abc"); *optStr3 = "cde"; EXPECT_EQ(s3, "abc"); // R-value ref version unique_ptr pInt(new int(3)); auto optIntPtr = folly::make_optional(std::move(pInt)); EXPECT_TRUE(pInt.get() == nullptr); ASSERT_TRUE(optIntPtr.has_value()); EXPECT_EQ(**optIntPtr, 3); // variadic version { auto&& optional = make_optional(int{}, double{}); std::ignore = optional; } { using Type = ConstructibleWithInitializerListAndArgsOnly; auto&& optional = make_optional({int{}}, double{}); std::ignore = optional; } } TEST(Optional, InitializerListConstruct) { using Type = ConstructibleWithInitializerListAndArgsOnly; auto&& optional = Optional{std::in_place, {int{}}, double{}}; std::ignore = optional; } TEST(Optional, TestDisambiguationMakeOptionalVariants) { { auto optional = make_optional(1); std::ignore = optional; } { auto optional = make_optional(1); std::ignore = optional; } } TEST(Optional, SelfAssignment) { Optional a = 42; a = static_cast(a); // suppress self-assign warning ASSERT_TRUE(a.has_value() && a.value() == 42); Optional b = 23333333; b = static_cast(b); // suppress self-move warning ASSERT_TRUE(b.has_value() && b.value() == 23333333); } namespace { class ContainsOptional { public: ContainsOptional() {} explicit ContainsOptional(int x) : opt_(x) {} bool hasValue() const { return opt_.has_value(); } int value() const { return opt_.value(); } ContainsOptional(const ContainsOptional& other) = default; ContainsOptional& operator=(const ContainsOptional& other) = default; ContainsOptional(ContainsOptional&& other) = default; ContainsOptional& operator=(ContainsOptional&& other) = default; private: Optional opt_; }; } // namespace /** * Test that a class containing an Optional can be copy and move assigned. */ TEST(Optional, AssignmentContained) { { ContainsOptional source(5), target; target = source; EXPECT_TRUE(target.hasValue()); EXPECT_EQ(5, target.value()); } { ContainsOptional source(5), target; target = std::move(source); EXPECT_TRUE(target.hasValue()); EXPECT_EQ(5, target.value()); EXPECT_FALSE(source.hasValue()); } { ContainsOptional opt_uninit, target(10); target = opt_uninit; EXPECT_FALSE(target.hasValue()); } } TEST(Optional, Exceptions) { Optional empty; EXPECT_THROW(empty.value(), OptionalEmptyException); } TEST(Optional, NoThrowDefaultConstructible) { EXPECT_TRUE(std::is_nothrow_default_constructible>::value); } namespace { struct NoDestructor {}; struct WithDestructor { ~WithDestructor(); }; } // namespace TEST(Optional, TriviallyDestructible) { // These could all be static_asserts but EXPECT_* give much nicer output on // failure. EXPECT_TRUE(std::is_trivially_destructible>::value); EXPECT_TRUE(std::is_trivially_destructible>::value); EXPECT_FALSE(std::is_trivially_destructible>::value); } TEST(Optional, Hash) { // Test it's usable in std::unordered map (compile time check) std::unordered_map, Optional> obj; // Also check the std::hash template can be instantiated by the compiler std::hash>()(none); std::hash>()(3); } namespace { struct WithConstMember { /* implicit */ WithConstMember(int val) : x(val) {} const int x; }; // Make this opaque to the optimizer by preventing inlining. FOLLY_NOINLINE void replaceWith2(Optional& o) { o.emplace(2); } } // namespace TEST(Optional, ConstMember) { // Verify that the compiler doesn't optimize out the second load of // o->x based on the assumption that the field is const. // // Current Optional implementation doesn't defend against that // assumption, thus replacing an optional where the object has const // members is technically UB and would require wrapping each access // to the storage with std::launder, but this prevents useful // optimizations. // // Implementations of std::optional in both libstdc++ and libc++ are // subject to the same UB. It is then reasonable to believe that // major compilers don't rely on the constness assumption. Optional o(1); int sum = 0; sum += o->x; replaceWith2(o); sum += o->x; EXPECT_EQ(sum, 3); } TEST(Optional, NoneMatchesNullopt) { auto op = make_optional(10); op = {}; EXPECT_FALSE(op.has_value()); op = make_optional(20); op = none; EXPECT_FALSE(op.has_value()); } TEST(Optional, StdOptionalConversions) { folly::Optional f = 42; std::optional s = static_cast>(f); EXPECT_EQ(*s, 42); EXPECT_TRUE(f); f = static_cast>(s); EXPECT_EQ(*f, 42); EXPECT_TRUE(s); const folly::Optional fc = 12; s = static_cast>(fc); EXPECT_EQ(*s, 12); folly::Optional> fp = std::make_unique(42); std::optional> sp(std::move(fp)); EXPECT_EQ(**sp, 42); EXPECT_FALSE(fp); fp = static_cast>>(std::move(sp)); EXPECT_EQ(**fp, 42); EXPECT_FALSE(sp); } TEST(Optional, MovedFromOptionalIsEmpty) { // moved-from folly::Optional is empty, unlike std::optional! folly::Optional x = 1; EXPECT_TRUE(x); auto y = std::move(x); // NOLINTNEXTLINE(bugprone-use-after-move) EXPECT_FALSE(x); EXPECT_TRUE(y); folly::Optional z; z = std::move(y); // NOLINTNEXTLINE(bugprone-use-after-move) EXPECT_FALSE(y); EXPECT_TRUE(z); folly::Optional str("hello"); EXPECT_TRUE(str); auto str2 = std::move(str); // NOLINTNEXTLINE(bugprone-use-after-move) EXPECT_FALSE(str); EXPECT_TRUE(str2); folly::Optional str3; str3 = std::move(str2); // NOLINTNEXTLINE(bugprone-use-after-move) EXPECT_FALSE(str2); EXPECT_TRUE(str3); } } // namespace folly