Use std::optional for chip::Nullable (#33080)

* make nullable depend on std::optional instead of chip::optional

* Fix equality operator

* Fix inequality operator as well

* Fix test: previous code was enforcing chip::optional behavior on in-place construction

* Restyle

* Fix some compile errors ... std::optional now detects unused variables

* Restyle

* Remove unused variable

* Restyle

* Review updates

* Review fix

* Typo fixes

* Ugly forward of Value and ValueOr .... however at least hopefully it is complete

* Do not expose value/value_or at this time and keep Value/ValueOr as the public API for nullable

* Fix copy and paste typo

* Make more changes to make things compile and work like the old nullable

* Use value again instead of operator** ... I was looking at the wrong compiler errors

* Undo unrelated change

* Make clang-tidy be abel to compile ... apparently it really does not like using has_value in equality operators

* Restyle

* Another update to make clang-tidy happy for the other equality operator

* Update src/app/data-model/Nullable.h

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>

* Undo odd whitespace change

---------

Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server.cpp b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server.cpp
index 82dd454..02486be 100644
--- a/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server.cpp
+++ b/src/app/clusters/valve-configuration-and-control-server/valve-configuration-and-control-server.cpp
@@ -277,8 +277,7 @@
 
 CHIP_ERROR CloseValve(EndpointId ep)
 {
-    Delegate * delegate = GetDelegate(ep);
-    DataModel::Nullable<uint32_t> rDuration;
+    Delegate * delegate        = GetDelegate(ep);
     CHIP_ERROR attribute_error = CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
 
     VerifyOrReturnError(Status::Success == TargetState::Set(ep, ValveConfigurationAndControl::ValveStateEnum::kClosed),
@@ -309,10 +308,8 @@
 
 CHIP_ERROR SetValveLevel(EndpointId ep, DataModel::Nullable<Percent> level, DataModel::Nullable<uint32_t> openDuration)
 {
-    Delegate * delegate     = GetDelegate(ep);
-    Optional<Status> status = Optional<Status>::Missing();
-    DataModel::Nullable<Percent> openLevel;
-    DataModel::Nullable<uint64_t> autoCloseTime;
+    Delegate * delegate        = GetDelegate(ep);
+    Optional<Status> status    = Optional<Status>::Missing();
     CHIP_ERROR attribute_error = CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
 
     if (HasFeature(ep, ValveConfigurationAndControl::Feature::kTimeSync))
@@ -328,6 +325,7 @@
             VerifyOrReturnError(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), CHIP_ERROR_INVALID_TIME);
 
             uint64_t time = openDuration.Value() * chip::kMicrosecondsPerSecond;
+            DataModel::Nullable<uint64_t> autoCloseTime;
             autoCloseTime.SetNonNull(chipEpochTime + time);
             VerifyOrReturnError(Status::Success == AutoCloseTime::Set(ep, autoCloseTime), attribute_error);
         }
diff --git a/src/app/data-model/Nullable.h b/src/app/data-model/Nullable.h
index cea3dea..f56e67c 100644
--- a/src/app/data-model/Nullable.h
+++ b/src/app/data-model/Nullable.h
@@ -19,9 +19,9 @@
 #pragma once
 
 #include <app/util/attribute-storage-null-handling.h>
-#include <lib/core/Optional.h>
-
+#include <optional>
 #include <type_traits>
+#include <utility>
 
 namespace chip {
 namespace app {
@@ -37,31 +37,40 @@
  * things.
  */
 template <typename T>
-struct Nullable : protected Optional<T>
+struct Nullable : protected std::optional<T>
 {
+
     //
     // The following 'using' statement is needed to make visible
     // all constructors of the base class within this derived class.
     //
-    using Optional<T>::Optional;
+    using std::optional<T>::optional;
+    using std::optional<T>::operator*;
+    using std::optional<T>::operator->;
 
-    // Pull in APIs that make sense on Nullable with the same names as on
-    // Optional.
-    using Optional<T>::Value;
-    using Optional<T>::ValueOr;
+    Nullable(NullOptionalType) : std::optional<T>(std::nullopt) {}
 
     // Some consumers need an easy way to determine our underlying type.
     using UnderlyingType = T;
 
-    constexpr void SetNull() { Optional<T>::ClearValue(); }
-    constexpr bool IsNull() const { return !Optional<T>::HasValue(); }
+    constexpr void SetNull() { std::optional<T>::reset(); }
+    constexpr bool IsNull() const { return !std::optional<T>::has_value(); }
 
     template <class... Args>
     constexpr T & SetNonNull(Args &&... args)
     {
-        return Optional<T>::Emplace(std::forward<Args>(args)...);
+        return std::optional<T>::emplace(std::forward<Args>(args)...);
     }
 
+    template <typename... Args>
+    constexpr auto ValueOr(Args &&... args) const
+    {
+        return std::optional<T>::value_or(std::forward<Args>(args)...);
+    }
+
+    inline constexpr const T & Value() const { return std::optional<T>::value(); }
+    inline T & Value() { return std::optional<T>::value(); }
+
     // For integer types, being nullable involves a range restriction.
     template <
         typename U = std::decay_t<T>,
@@ -96,22 +105,26 @@
     // The only fabric-scoped objects in the spec are commands, events and structs inside lists, and none of those can be nullable.
     static constexpr bool kIsFabricScoped = false;
 
-    bool operator==(const Nullable & other) const { return Optional<T>::operator==(other); }
-    bool operator!=(const Nullable & other) const { return Optional<T>::operator!=(other); }
-    bool operator==(const T & other) const { return Optional<T>::operator==(other); }
-    bool operator!=(const T & other) const { return Optional<T>::operator!=(other); }
+    inline bool operator==(const T & other) const { return static_cast<const std::optional<T> &>(*this) == other; }
+    inline bool operator!=(const T & other) const { return !(*this == other); }
+
+    inline bool operator==(const Nullable<T> & other) const
+    {
+        return static_cast<const std::optional<T> &>(*this) == static_cast<const std::optional<T> &>(other);
+    }
+    inline bool operator!=(const Nullable<T> & other) const { return !(*this == other); }
 };
 
 template <class T>
 constexpr Nullable<std::decay_t<T>> MakeNullable(T && value)
 {
-    return Nullable<std::decay_t<T>>(InPlace, std::forward<T>(value));
+    return Nullable<std::decay_t<T>>(std::in_place, std::forward<T>(value));
 }
 
 template <class T, class... Args>
 constexpr Nullable<T> MakeNullable(Args &&... args)
 {
-    return Nullable<T>(InPlace, std::forward<Args>(args)...);
+    return Nullable<T>(std::in_place, std::forward<Args>(args)...);
 }
 
 } // namespace DataModel
diff --git a/src/app/tests/TestNullable.cpp b/src/app/tests/TestNullable.cpp
index c1290c1..6600373 100644
--- a/src/app/tests/TestNullable.cpp
+++ b/src/app/tests/TestNullable.cpp
@@ -45,6 +45,7 @@
     CtorDtorCounter & operator=(CtorDtorCounter &&) = default;
 
     bool operator==(const CtorDtorCounter & o) const { return m == o.m; }
+    bool operator!=(const CtorDtorCounter & o) const { return m != o.m; }
 
     int m;
 
@@ -68,6 +69,9 @@
 
     MovableCtorDtorCounter(MovableCtorDtorCounter && o)           = default;
     MovableCtorDtorCounter & operator=(MovableCtorDtorCounter &&) = default;
+
+    using CtorDtorCounter::operator==;
+    using CtorDtorCounter::operator!=;
 };
 
 int CtorDtorCounter::created   = 0;
@@ -165,24 +169,26 @@
     CtorDtorCounter::ResetCounter();
 
     {
-        auto testSrc = MakeNullable<MovableCtorDtorCounter>(400);
-        Nullable<MovableCtorDtorCounter> testDst(std::move(testSrc));
-        NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 1);
+        auto testSrc = MakeNullable<MovableCtorDtorCounter>(400);     // construct
+        Nullable<MovableCtorDtorCounter> testDst(std::move(testSrc)); // move construct
+        NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 0);
         NL_TEST_ASSERT(inSuite, !testDst.IsNull() && testDst.Value().m == 400);
+        // destroy both testsSrc and testDst
     }
     NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2);
 
+    CtorDtorCounter::ResetCounter();
     {
-        Nullable<MovableCtorDtorCounter> testDst;
-        NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2);
+        Nullable<MovableCtorDtorCounter> testDst; // no object construction
+        NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 0 && CtorDtorCounter::destroyed == 0);
         NL_TEST_ASSERT(inSuite, !!testDst.IsNull());
 
-        auto testSrc = MakeNullable<MovableCtorDtorCounter>(401);
-        testDst      = std::move(testSrc);
-        NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 3);
+        auto testSrc = MakeNullable<MovableCtorDtorCounter>(401); // construct object
+        testDst      = std::move(testSrc);                        // construct a copy
+        NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 0);
         NL_TEST_ASSERT(inSuite, !testDst.IsNull() && testDst.Value().m == 401);
     }
-    NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 4);
+    NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2);
 }
 
 static void TestUpdate(nlTestSuite * inSuite, void * inContext)