async: add context_ref class for CRTP patterns

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I6364f6f5a654ec0c628112637014ae2daed5d740
diff --git a/include/sdbusplus/async/client.hpp b/include/sdbusplus/async/client.hpp
index 4bece52..ab2e9e9 100644
--- a/include/sdbusplus/async/client.hpp
+++ b/include/sdbusplus/async/client.hpp
@@ -18,10 +18,10 @@
  */
 template <bool S, bool P, bool Preserved, template <typename> typename... Types>
 class client :
+    public context_ref,
     public Types<sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved>>...
 {
   private:
-    sdbusplus::async::context& ctx;
     sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved> proxy{};
 
   public:
@@ -33,14 +33,14 @@
     /* Default (empty) constructor only when Service and Path are missing. */
     explicit client(sdbusplus::async::context& ctx)
         requires(!S && !P)
-        : Types<decltype(proxy)>(ctx, decltype(proxy){})..., ctx(ctx)
+        : context_ref(ctx), Types<decltype(proxy)>(ctx, decltype(proxy){})...
     {}
 
     /* Conversion constructor for a non-empty (Service and/or Path) proxy. */
     explicit client(sdbusplus::async::context& ctx,
                     sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved> p)
         requires(S || P)
-        : Types<decltype(proxy)>(ctx, p)..., ctx(ctx), proxy(p)
+        : context_ref(ctx), Types<decltype(proxy)>(ctx, p)..., proxy(p)
     {}
 
     /* Convert a non-Service instance to a Service instance. */
diff --git a/include/sdbusplus/async/context.hpp b/include/sdbusplus/async/context.hpp
index 96cb38d..40356d5 100644
--- a/include/sdbusplus/async/context.hpp
+++ b/include/sdbusplus/async/context.hpp
@@ -125,6 +125,25 @@
     static int dbus_event_handle(sd_event_source*, int, uint32_t, void*);
 };
 
+/** @brief Simple holder of a context&
+ *
+ *  A common pattern is for classes to hold a reference to a
+ *  context.  Add a class that can be inherited instead of
+ *  having as a class member.  This allows the context-ref constructor to be
+ * placed earliest in the ctor initializer list, so that the reference can be
+ * available from inherited classes (ex. for CRTP patterns).
+ *
+ */
+class context_ref
+{
+  public:
+    context_ref() = delete;
+    explicit context_ref(context& ctx) : ctx(ctx) {}
+
+  protected:
+    context& ctx;
+};
+
 namespace details
 {
 struct context_friend
diff --git a/include/sdbusplus/async/timer.hpp b/include/sdbusplus/async/timer.hpp
index 699265a..909fad3 100644
--- a/include/sdbusplus/async/timer.hpp
+++ b/include/sdbusplus/async/timer.hpp
@@ -29,13 +29,13 @@
  * On callback, completes the Receiver.
  */
 template <execution::receiver R>
-struct sleep_operation : public details::context_friend
+struct sleep_operation : public context_ref, details::context_friend
 {
     sleep_operation() = delete;
     sleep_operation(sleep_operation&&) = delete;
 
     sleep_operation(context& ctx, event_t::time_resolution time, R&& r) :
-        ctx(ctx), time(time), receiver(std::move(r))
+        context_ref(ctx), time(time), receiver(std::move(r))
     {}
 
     static int handler(sd_event_source*, uint64_t, void* data) noexcept
@@ -66,7 +66,6 @@
         return get_event_loop(ctx);
     }
 
-    context& ctx;
     event_t::time_resolution time;
     event_source_t source;
     R receiver;
@@ -76,14 +75,14 @@
  *
  *  On connect, instantiates the completion event.
  */
-struct sleep_sender : public details::context_friend
+struct sleep_sender : public context_ref, details::context_friend
 {
     using is_sender = void;
 
     sleep_sender() = delete;
 
     sleep_sender(context& ctx, event_t::time_resolution time) noexcept :
-        ctx(ctx), time(time)
+        context_ref(ctx), time(time)
     {}
 
     friend auto tag_invoke(execution::get_completion_signatures_t,
@@ -110,7 +109,6 @@
     }
 
   private:
-    context& ctx;
     event_t::time_resolution time;
 };
 
diff --git a/src/async/context.cpp b/src/async/context.cpp
index 5a5bc8c..5bb58ae 100644
--- a/src/async/context.cpp
+++ b/src/async/context.cpp
@@ -22,9 +22,9 @@
  *  task `co_await`ing Senders over and over.  This class is the completion
  *  handling for the Sender (to get it back to the Receiver, ie. the worker).
  */
-struct wait_process_completion : bus::details::bus_friend
+struct wait_process_completion : context_ref, bus::details::bus_friend
 {
-    explicit wait_process_completion(context& ctx) : ctx(ctx) {}
+    explicit wait_process_completion(context& ctx) : context_ref(ctx) {}
     virtual ~wait_process_completion() = default;
 
     // Called by the `caller` to indicate the Sender is completed.
@@ -36,7 +36,6 @@
     void arm() noexcept;
 
     // Data to share with the worker.
-    context& ctx;
     event_t::time_resolution timeout{};
 
     // TODO: It seems like we should be able to do a normal `task<>` here
@@ -82,11 +81,11 @@
 };
 
 /* The sender for the wait/process event. */
-struct wait_process_sender
+struct wait_process_sender : public context_ref
 {
     using is_sender = void;
 
-    explicit wait_process_sender(context& ctx) : ctx(ctx) {}
+    explicit wait_process_sender(context& ctx) : context_ref(ctx) {}
 
     friend auto tag_invoke(execution::get_completion_signatures_t,
                            const wait_process_sender&, auto)
@@ -99,9 +98,6 @@
         // Create the completion for the wait.
         return {self.ctx, std::move(r)};
     }
-
-  private:
-    context& ctx;
 };
 
 exec::basic_task<void, exec::__task::__raw_task_context<void>>