async: client: use CRTP to eliminate excess context refs

The types used to create a client proxy object need to have access
to the async::context, but the sdbusplus::async::client_t that holds
them already has a reference.  Use CRTP so that there is only a single
held reference per client, no matter how many types it contains.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I9b3356a6d28f09f10dea74ca310812c7cb9acfa0
diff --git a/include/sdbusplus/async/client.hpp b/include/sdbusplus/async/client.hpp
index ab2e9e9..186ecdc 100644
--- a/include/sdbusplus/async/client.hpp
+++ b/include/sdbusplus/async/client.hpp
@@ -8,6 +8,11 @@
 namespace client
 {
 
+namespace details
+{
+struct client_context_friend;
+}
+
 /** An aggregation class of sdbusplus::async::proxy-based client types.
  *
  *  The resulting class acts as a union of all Types from the template
@@ -16,13 +21,20 @@
  *  Like a `proxy`, the class only becomes functional once the service and
  *  path are populated.
  */
-template <bool S, bool P, bool Preserved, template <typename> typename... Types>
+template <bool S, bool P, bool Preserved,
+          template <typename, typename> typename... Types>
 class client :
     public context_ref,
-    public Types<sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved>>...
+    public Types<client<S, P, Preserved, Types...>,
+                 sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved>>...
 {
+  public:
+    using Self = client<S, P, Preserved, Types...>;
+    using Proxy = sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved>;
+    friend details::client_context_friend;
+
   private:
-    sdbusplus::async::proxy_ns::proxy<S, P, false, Preserved> proxy{};
+    Proxy proxy{};
 
   public:
     constexpr client() = delete;
@@ -33,14 +45,13 @@
     /* Default (empty) constructor only when Service and Path are missing. */
     explicit client(sdbusplus::async::context& ctx)
         requires(!S && !P)
-        : context_ref(ctx), Types<decltype(proxy)>(ctx, decltype(proxy){})...
+        : context_ref(ctx), Types<Self, Proxy>(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)
+    explicit client(sdbusplus::async::context& ctx, Proxy p)
         requires(S || P)
-        : context_ref(ctx), Types<decltype(proxy)>(ctx, p)..., proxy(p)
+        : context_ref(ctx), Types<Self, Proxy>(p)..., proxy(p)
     {}
 
     /* Convert a non-Service instance to a Service instance. */
@@ -65,14 +76,31 @@
  *  This holds Service/Path in string-views, which must exist longer than
  *  the lifetime of this client_t.
  */
-template <template <typename> typename... Types>
+template <template <typename, typename> typename... Types>
 using client_t = client::client<false, false, false, Types...>;
 /** A Preserved client alias.
  *
  *  This holds Service/Path in strings, which thus have lifetimes that are
  *  the same as the client itself.
  */
-template <template <typename> typename... Types>
+template <template <typename, typename> typename... Types>
 using client_preserved_t = client::client<false, false, false, Types...>;
 
+namespace client::details
+{
+/* Indirect so that the generated Types can access the client_t's context.
+ *
+ * If P2893 gets into C++26 we could eliminate this because we can set all
+ * the Types as friends directly.
+ */
+struct client_context_friend
+{
+    template <typename T>
+    sdbusplus::async::context& context()
+    {
+        return static_cast<T*>(this)->ctx;
+    }
+};
+} // namespace client::details
+
 } // namespace sdbusplus::async
diff --git a/tools/sdbusplus/templates/interface.client.hpp.mako b/tools/sdbusplus/templates/interface.client.hpp.mako
index 8c111bc..e5ef488 100644
--- a/tools/sdbusplus/templates/interface.client.hpp.mako
+++ b/tools/sdbusplus/templates/interface.client.hpp.mako
@@ -23,7 +23,7 @@
 namespace details
 {
 // forward declaration
-template <typename Proxy>
+template <typename Client, typename Proxy>
 class ${interface.classname};
 } // namespace details
 
@@ -33,17 +33,17 @@
  *  sdbusplus::async::client_t<${interface.classname}>() or
  *  ${interface.classname}() both construct an equivalent instance.
  */
-template <typename Proxy = void>
+template <typename Client = void, typename Proxy = void>
 struct ${interface.classname} :
-    public std::conditional_t<std::is_void_v<Proxy>,
+    public std::conditional_t<std::is_void_v<Client>,
                               sdbusplus::async::client_t<details::${interface.classname}>,
-                              details::${interface.classname}<Proxy>>
+                              details::${interface.classname}<Client, Proxy>>
 {
     template <typename... Args>
     ${interface.classname}(Args&&... args) :
-        std::conditional_t<std::is_void_v<Proxy>,
+        std::conditional_t<std::is_void_v<Client>,
                            sdbusplus::async::client_t<details::${interface.classname}>,
-                           details::${interface.classname}<Proxy>>(
+                           details::${interface.classname}<Client, Proxy>>(
             std::forward<Args>(args)...)
     {}
 };
@@ -51,12 +51,13 @@
 namespace details
 {
 
-template <typename Proxy>
-class ${interface.classname} : public sdbusplus::common::${interface.cppNamespacedClass()}
+template <typename Client, typename Proxy>
+class ${interface.classname} :
+    public sdbusplus::common::${interface.cppNamespacedClass()},
+    public sdbusplus::async::client::details::client_context_friend
 {
   public:
-    template <bool, bool, bool, template <typename> typename...>
-    friend class sdbusplus::async::client::client;
+    friend Client;
     template <typename>
     friend struct sdbusplus::client::${interface.cppNamespacedClass()};
 
@@ -72,11 +73,16 @@
     % endfor
   private:
     // Conversion constructor from proxy used by client_t.
-    constexpr ${interface.classname}(sdbusplus::async::context& ctx, Proxy p) :
-        ctx(ctx), proxy(p.interface(interface))
+    explicit constexpr ${interface.classname}(Proxy p) :
+        proxy(p.interface(interface))
     {}
 
-    sdbusplus::async::context& ctx;
+    sdbusplus::async::context& context()
+    {
+        return sdbusplus::async::client::details::client_context_friend::
+            context<Client>();
+    }
+
     decltype(std::declval<Proxy>().interface(interface)) proxy = {};
 };
 
diff --git a/tools/sdbusplus/templates/method.client.hpp.mako b/tools/sdbusplus/templates/method.client.hpp.mako
index 16bbfce..8001370 100644
--- a/tools/sdbusplus/templates/method.client.hpp.mako
+++ b/tools/sdbusplus/templates/method.client.hpp.mako
@@ -22,7 +22,7 @@
 )
     {
         return proxy.template call<\
-${method.returns_as_list(interface)}>(ctx, "${method.name}"\
+${method.returns_as_list(interface)}>(context(), "${method.name}"\
     % if len(method.parameters) != 0:
 , ${method.parameters_as_list()}\
     % else:
diff --git a/tools/sdbusplus/templates/property.client.hpp.mako b/tools/sdbusplus/templates/property.client.hpp.mako
index 46be6ea..c03ee3f 100644
--- a/tools/sdbusplus/templates/property.client.hpp.mako
+++ b/tools/sdbusplus/templates/property.client.hpp.mako
@@ -4,7 +4,7 @@
     auto ${property.camelCase}()
     {
         return proxy.template get_property<\
-${property.cppTypeParam(interface.name)}>(ctx, "${property.name}");
+${property.cppTypeParam(interface.name)}>(context(), "${property.name}");
     }
 
 % if 'const' not in property.flags and 'readonly' not in property.flags:
@@ -15,6 +15,6 @@
     {
         return proxy.template set_property<\
 ${property.cppTypeParam(interface.name)}>(
-            ctx, "${property.name}", std::forward<decltype(value)>(value));
+            context(), "${property.name}", std::forward<decltype(value)>(value));
     }
 % endif