server: object: reduce SFINAE

Use C++20 Concepts to reduce SFINAE in object.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I22be05b28a30cabafc10ca4f0c9eef124a20bc7f
diff --git a/include/sdbusplus/server/object.hpp b/include/sdbusplus/server/object.hpp
index c3eaeea..c86598b 100644
--- a/include/sdbusplus/server/object.hpp
+++ b/include/sdbusplus/server/object.hpp
@@ -14,10 +14,6 @@
 namespace object
 {
 
-// Forward declaration.
-template <class... Args>
-struct object;
-
 namespace details
 {
 
@@ -25,121 +21,6 @@
 template <class... Args>
 struct compose;
 
-/** Template to identify if an inheritance is a "normal" type or a nested
- *  sdbusplus::object.
- */
-template <class T>
-struct compose_inherit
-{
-    // This is a normal type.
-    using type = T;
-
-    // Normal types need emit_added called, if possible.
-    template <class S>
-    static void maybe_emit_iface_added(S* obj)
-    {
-        if constexpr (has_emit_added<S>())
-        {
-            obj->S::emit_added();
-        }
-    }
-
-    // Test if emit_added() exists in T return std::true_type.
-    template <class S>
-    static constexpr auto has_emit_added_helper(int)
-        -> decltype(std::declval<S>().emit_added(), std::true_type{});
-
-    // If the above test fails, fall back to this to return std::false_type
-    template <class>
-    static constexpr std::false_type has_emit_added_helper(...);
-
-    // Invoke the test with an int so it first resolves to
-    // has_emit_added_helper(int), and when it fails, it resovles to
-    // has_emit_added_helper(...) thanks to SFINAE.
-    // So the return type is std::true_type if emit_added() exists in T and
-    // std::false_type otherwise.
-    template <class S>
-    using has_emit_added = decltype(has_emit_added_helper<S>(0));
-};
-
-/** Template specialization for the sdbusplus::object nesting. */
-template <class... Args>
-struct compose_inherit<object<Args...>>
-{
-    // Unravel the inheritance with a recursive compose.
-    using type = compose<Args...>;
-
-    // Redirect the interface added to the composed parts.
-    template <class T>
-    static void maybe_emit_iface_added(T* obj)
-    {
-        obj->compose<Args...>::maybe_emit_iface_added();
-    }
-};
-
-/** std-style _t alias for compose_inherit. */
-template <class... Args>
-using compose_inherit_t = typename compose_inherit<Args...>::type;
-
-/** Templates to allow multiple inheritance via template parameters.
- *
- *  These allow an object to group multiple dbus interface bindings into a
- *  single class.
- */
-template <class T, class... Rest>
-struct compose_impl : compose_inherit_t<T>, compose_impl<Rest...>
-{
-    compose_impl(bus_t& bus, const char* path) :
-        T(bus, path), compose_impl<Rest...>(bus, path)
-    {}
-
-    void maybe_emit_iface_added()
-    {
-        compose_inherit<T>::maybe_emit_iface_added(
-            static_cast<compose_inherit_t<T>*>(this));
-        compose_impl<Rest...>::maybe_emit_iface_added();
-    }
-};
-
-/** Specialization for single element. */
-template <class T>
-struct compose_impl<T> : compose_inherit_t<T>
-{
-    compose_impl(bus_t& bus, const char* path) : compose_inherit_t<T>(bus, path)
-    {}
-
-    void maybe_emit_iface_added()
-    {
-        compose_inherit<T>::maybe_emit_iface_added(
-            static_cast<compose_inherit_t<T>*>(this));
-    }
-};
-
-/** Default compose operation for variadic arguments. */
-template <class... Args>
-struct compose : compose_impl<Args...>
-{
-    compose(bus_t& bus, const char* path) : compose_impl<Args...>(bus, path) {}
-
-    friend struct compose_inherit<object<Args...>>;
-
-  protected:
-    void maybe_emit_iface_added()
-    {
-        compose_impl<Args...>::maybe_emit_iface_added();
-    }
-};
-
-/** Specialization for zero variadic arguments. */
-template <>
-struct compose<>
-{
-    compose(bus_t& /*bus*/, const char* /*path*/) {}
-
-  protected:
-    void maybe_emit_iface_added() {}
-};
-
 } // namespace details
 
 /** Class to compose multiple dbus interfaces and object signals.
@@ -267,8 +148,66 @@
 
 } // namespace object
 
+// Type alias for object_t.
 template <class... Args>
 using object_t = object::object<Args...>;
 
+namespace object::details
+{
+
+/** Template to identify if an inheritance is a "normal" type or a nested
+ *  sdbusplus::object.
+ */
+template <class T>
+struct compose_inherit
+{
+    // This is a normal type.
+    using type = T;
+};
+
+/** Template specialization for the sdbusplus::object nesting. */
+template <class... Args>
+struct compose_inherit<object<Args...>>
+{
+    // Unravel the inheritance with a recursive compose.
+    using type = compose<Args...>;
+};
+
+/** std-style _t alias for compose_inherit. */
+template <class... Args>
+using compose_inherit_t = typename compose_inherit<Args...>::type;
+
+template <class... Args>
+struct compose : compose_inherit_t<Args>...
+{
+    compose(bus_t& bus, const char* path) :
+        compose_inherit<Args>::type(bus, path)... {};
+
+    /** Call emit_added on all the composed types. */
+    void maybe_emit_iface_added()
+    {
+        (try_emit<compose_inherit_t<Args>>(), ...);
+    }
+
+  protected:
+    /** Call emit_added for an individual composed type. */
+    template <class T>
+    void try_emit()
+    {
+        // If T is a normal class with emit_added, call it.
+        if constexpr (requires(T t) { t.emit_added(); })
+        {
+            this->T::emit_added();
+        }
+        // If T is a recursive object_t, call its maybe_emit_iface_added.
+        if constexpr (requires(T t) { t.maybe_emit_iface_added(); })
+        {
+            this->T::maybe_emit_iface_added();
+        }
+    }
+};
+
+} // namespace object::details
+
 } // namespace server
 } // namespace sdbusplus