cpp-common/bt2: add `bt2::OptionalBorrowedObject`
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 24 Nov 2023 18:05:58 +0000 (13:05 -0500)
committerSimon Marchi <simon.marchi@efficios.com>
Thu, 14 Dec 2023 15:57:04 +0000 (10:57 -0500)
An instance of this new class template manages an optional contained
borrowed object of type `ObjT`, that is, a borrowed object that may or
may not be present.

Such an object considers that a `nullptr` libbabeltrace2 object pointer
means none. Therefore, using a `bt2::OptionalBorrowedObject` isn't more
costly, in time and space, as using a libbabeltrace2 object pointer in
C, but offers the typical C++ optional interface.

There's no `bt2s::nullopt` equivalent: just call reset().

An optional borrowed object stores the library pointer itself, only
constructing `ObjT` when it's known that the library pointer isn't
`nullptr`.

The class is pretty straightforward, except that:

* It has a few constructors and assignment operators to make things such
  as this work:

      bt2::OptionalBorrowedObject<bt2::ConstValue> f(const bt2::StringValue val)
      {
          if (std::string {*val} == "meow") {
              return val;
          }

          return {};
      }

  Also, you may assign a `bt2::OptionalBorrowedObject<Y>` instance to a
  `bt2::OptionalBorrowedObject<X>` instance if `Y` inherits `X`.

* operator->() would need to return the address of some `ObjT` instance,
  but an optional borrowed object has none, so return a proxy instead.

The result is a very natural usage:

    std::uint64_t getCsValue(const bt2::OptionalBorrowedObject<bt2::ConstClockSnapshot> optCs)
    {
        return optCs ? optCs->value() : 0;
    }

When optimized, the resulting instructions are the same as for an
equivalent C function:

    getCsValue(bt2::Optional<bt2::ConstClockSnapshot>):
            test    rdi, rdi
            je      .L2
            jmp     bt_clock_snapshot_get_value
    .L2:
            xor     eax, eax
            ret

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: Ic873ea4ff8b0f084189ece3ac139680469c907d3
Reviewed-on: https://review.lttng.org/c/babeltrace/+/11438
Reviewed-by: Simon Marchi <simon.marchi@efficios.com>
Tested-by: jenkins <jenkins@lttng.org>
CI-Build: Simon Marchi <simon.marchi@efficios.com>

src/Makefile.am
src/cpp-common/bt2/borrowed-object.hpp
src/cpp-common/bt2/optional-borrowed-object.hpp [new file with mode: 0644]

index 88f54afb9b518e3ef1fa44970043400d7e1e5bf9..f158fe2724362eb05abdc28b30e419a223f00644 100644 (file)
@@ -31,6 +31,7 @@ noinst_HEADERS = \
        cpp-common/bt2/message.hpp \
        cpp-common/bt2/shared-object.hpp \
        cpp-common/bt2/raw-value-proxy.hpp \
+       cpp-common/bt2/optional-borrowed-object.hpp \
        cpp-common/bt2/plugin-dev.hpp \
        cpp-common/bt2/private-query-executor.hpp \
        cpp-common/bt2/self-component-class.hpp \
index f3b4fdcccec34daed47bafe45a60f8bbaf1668bf..fbfd5b10c35bbed058c2d6680d68eb752d03643a 100644 (file)
@@ -25,6 +25,9 @@ namespace bt2 {
  *
  * The user of a borrowed object, including methods of a derived class,
  * can call libObjPtr() to access the libbabeltrace2 object pointer.
+ *
+ * You may only build a borrowed object with a pointer which isn't
+ * `nullptr`. See `bt2::OptionalBorrowedObject` for an optional version.
  */
 template <typename LibObjT>
 class BorrowedObject
diff --git a/src/cpp-common/bt2/optional-borrowed-object.hpp b/src/cpp-common/bt2/optional-borrowed-object.hpp
new file mode 100644 (file)
index 0000000..100abf4
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2023 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_CPP_COMMON_BT2_OPTIONAL_BORROWED_OBJECT_HPP
+#define BABELTRACE_CPP_COMMON_BT2_OPTIONAL_BORROWED_OBJECT_HPP
+
+#include <type_traits>
+
+#include "common/assert.h"
+
+#include "borrowed-object-proxy.hpp"
+
+namespace bt2 {
+
+/*
+ * An instance of this class template manages an optional contained
+ * borrowed object of type `ObjT`, that is, a borrowed object that may
+ * or may not be present.
+ *
+ * Such an object considers that a `nullptr` libbabeltrace2 object
+ * pointer means none. Therefore, using an `OptionalBorrowedObject`
+ * isn't more costly, in time and space, as using a libbabeltrace2
+ * object pointer in C, but offers the typical C++ optional interface.
+ *
+ * There's no `bt2s::nullopt` equivalent: just use the default
+ * constructor or call reset().
+ *
+ * There are a constructors an assignment operators which accept an
+ * instance of another wrapper object or of another optional borrowed
+ * object. For those, static assertions make sure that the assignment
+ * would work with borrowed objects, for example:
+ *
+ *     auto sharedIntVal = createValue(23L);
+ *
+ *     OptionalBorrowedObject<Value> myVal {*sharedIntVal};
+ *
+ * This is needed because `OptionalBorrowedObject` only keeps a
+ * libbabeltrace2 pointer (`_mLibObjPtr`), therefore it doesn't
+ * automatically know the relation between wrapper classes.
+ */
+template <typename ObjT>
+class OptionalBorrowedObject final
+{
+public:
+    using Obj = ObjT;
+    using LibObjPtr = typename ObjT::LibObjPtr;
+
+    /*
+     * Builds an optional borrowed object without an object.
+     *
+     * Intentionally not explicit.
+     */
+    OptionalBorrowedObject() noexcept = default;
+
+    /*
+     * Builds an optional borrowed object with an instance of `ObjT`
+     * wrapping the libbabeltrace2 pointer `libObjPtr`.
+     *
+     * Intentionally not explicit.
+     */
+    OptionalBorrowedObject(const LibObjPtr libObjPtr) noexcept : _mLibObjPtr {libObjPtr}
+    {
+    }
+
+    /*
+     * Builds an optional borrowed object with an instance of `ObjT`
+     * constructed from `obj`.
+     *
+     * It must be possible to construct an instance of `ObjT` with an
+     * instance of `OtherObjT`.
+     *
+     * Intentionally not explicit.
+     */
+    template <typename OtherObjT>
+    OptionalBorrowedObject(const OtherObjT obj) noexcept : _mLibObjPtr {obj.libObjPtr()}
+    {
+        static_assert(std::is_constructible<ObjT, OtherObjT>::value,
+                      "`ObjT` is constructible with an instance of `OtherObjT`.");
+    }
+
+    /*
+     * Builds an optional borrowed object from `optObj`, with or without
+     * an object.
+     *
+     * It must be possible to construct an instance of `ObjT` with an
+     * instance of `OtherObjT`.
+     *
+     * Intentionally not explicit.
+     */
+    template <typename OtherObjT>
+    OptionalBorrowedObject(const OptionalBorrowedObject<OtherObjT> optObj) noexcept :
+        _mLibObjPtr {optObj.libObjPtr()}
+    {
+        static_assert(std::is_constructible<ObjT, OtherObjT>::value,
+                      "`ObjT` is constructible with an instance of `OtherObjT`.");
+    }
+
+    /*
+     * Makes this optional borrowed object have an instance of `ObjT`
+     * wrapping the libbabeltrace2 pointer `libObjPtr`.
+     */
+    OptionalBorrowedObject& operator=(const LibObjPtr libObjPtr) noexcept
+    {
+        _mLibObjPtr = libObjPtr;
+        return *this;
+    }
+
+    /*
+     * Makes this optional borrowed object have an instance of `ObjT`
+     * constructed from `obj`.
+     *
+     * It must be possible to construct an instance of `ObjT` with an
+     * instance of `OtherObjT`.
+     */
+    template <typename OtherObjT>
+    OptionalBorrowedObject& operator=(const ObjT obj) noexcept
+    {
+        static_assert(std::is_constructible<ObjT, OtherObjT>::value,
+                      "`ObjT` is constructible with an instance of `OtherObjT`.");
+        _mLibObjPtr = obj.libObjPtr();
+        return *this;
+    }
+
+    /*
+     * Sets this optional borrowed object to `optObj`.
+     *
+     * It must be possible to construct an instance of `ObjT` with an
+     * instance of `OtherObjT`.
+     */
+    template <typename OtherObjT>
+    OptionalBorrowedObject& operator=(const OptionalBorrowedObject<ObjT> optObj) noexcept
+    {
+        static_assert(std::is_constructible<ObjT, OtherObjT>::value,
+                      "`ObjT` is constructible with an instance of `OtherObjT`.");
+        _mLibObjPtr = optObj.libObjPtr();
+        return *this;
+    }
+
+    /* Wrapped libbabeltrace2 object pointer (may be `nullptr`) */
+    LibObjPtr libObjPtr() const noexcept
+    {
+        return _mLibObjPtr;
+    }
+
+    ObjT object() const noexcept
+    {
+        BT_ASSERT_DBG(_mLibObjPtr);
+        return ObjT {_mLibObjPtr};
+    }
+
+    ObjT operator*() const noexcept
+    {
+        return this->object();
+    }
+
+    /*
+     * We want to return the address of an `ObjT` instance here, but we
+     * only have a library pointer (`_mLibObjPtr`) because an `ObjT`
+     * instance may not wrap `nullptr`.
+     *
+     * Therefore, return a proxy object which holds an internal
+     * `ObjT` instance and implements operator->() itself.
+     *
+     * In other words, doing `myOptObj->something()` is equivalent to
+     * calling `myOptObj.operator->().operator->()->something()`.
+     */
+    BorrowedObjectProxy<ObjT> operator->() const noexcept
+    {
+        return BorrowedObjectProxy<ObjT> {_mLibObjPtr};
+    }
+
+    bool hasObject() const noexcept
+    {
+        return _mLibObjPtr;
+    }
+
+    operator bool() const noexcept
+    {
+        return this->hasObject();
+    }
+
+    void reset() noexcept
+    {
+        _mLibObjPtr = nullptr;
+    }
+
+private:
+    typename ObjT::LibObjPtr _mLibObjPtr = nullptr;
+};
+
+} /* namespace bt2 */
+
+#endif /* BABELTRACE_CPP_COMMON_BT2_OPTIONAL_BORROWED_OBJECT_HPP */
This page took 0.027434 seconds and 4 git commands to generate.