From cb8ac519624c857fe7b08b799cd9e3067a852284 Mon Sep 17 00:00:00 2001
From: Marco Clemencic <marco.clemencic@cern.ch>
Date: Tue, 18 Mar 2025 14:34:26 +0100
Subject: [PATCH] Allow concrete services to be used in ServiceHandle<T>

Modify ServiceHandle to fall back on dynamic_cast if the template
argument is not an abstract interface. This is similar to the way
ToolHandle works for concrete tools.

This also adds the new header Gaudi/Concepts.h to host C++20 concepts of
common utility, starting from Gaudi::IsInterface<T>.
---
 GaudiKernel/include/Gaudi/Concepts.h          | 22 ++++++++
 .../include/GaudiKernel/ServiceHandle.h       | 25 +++++++--
 GaudiTestSuite/CMakeLists.txt                 |  1 +
 GaudiTestSuite/src/UseSvcWithoutInterface.cpp | 45 ++++++++++++++++
 .../handles/test_svc_without_interface.py     | 53 +++++++++++++++++++
 5 files changed, 143 insertions(+), 3 deletions(-)
 create mode 100644 GaudiKernel/include/Gaudi/Concepts.h
 create mode 100644 GaudiTestSuite/src/UseSvcWithoutInterface.cpp
 create mode 100644 GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py

diff --git a/GaudiKernel/include/Gaudi/Concepts.h b/GaudiKernel/include/Gaudi/Concepts.h
new file mode 100644
index 0000000000..c443698a70
--- /dev/null
+++ b/GaudiKernel/include/Gaudi/Concepts.h
@@ -0,0 +1,22 @@
+/***********************************************************************************\
+* (c) Copyright 2025 CERN for the benefit of the LHCb and ATLAS collaborations      *
+*                                                                                   *
+* This software is distributed under the terms of the Apache version 2 licence,     *
+* copied verbatim in the file "LICENSE".                                            *
+*                                                                                   *
+* In applying this licence, CERN does not waive the privileges and immunities       *
+* granted to it by virtue of its status as an Intergovernmental Organization        *
+* or submit itself to any jurisdiction.                                             *
+\***********************************************************************************/
+#pragma once
+
+#include <concepts>
+
+class InterfaceID;
+
+namespace Gaudi {
+  template <typename T>
+  concept IsInterface = requires {
+    { T::interfaceID() } -> std::same_as<const InterfaceID&>;
+  };
+} // namespace Gaudi
diff --git a/GaudiKernel/include/GaudiKernel/ServiceHandle.h b/GaudiKernel/include/GaudiKernel/ServiceHandle.h
index 20c4aea33a..067b9da8d5 100644
--- a/GaudiKernel/include/GaudiKernel/ServiceHandle.h
+++ b/GaudiKernel/include/GaudiKernel/ServiceHandle.h
@@ -1,5 +1,5 @@
 /***********************************************************************************\
-* (c) Copyright 1998-2024 CERN for the benefit of the LHCb and ATLAS collaborations *
+* (c) Copyright 1998-2025 CERN for the benefit of the LHCb and ATLAS collaborations *
 *                                                                                   *
 * This software is distributed under the terms of the Apache version 2 licence,     *
 * copied verbatim in the file "LICENSE".                                            *
@@ -12,6 +12,7 @@
 #define GAUDIKERNEL_SERVICEHANDLE_H
 
 // Includes
+#include <Gaudi/Concepts.h>
 #include <GaudiKernel/Bootstrap.h>
 #include <GaudiKernel/GaudiException.h>
 #include <GaudiKernel/GaudiHandle.h>
@@ -94,9 +95,27 @@ public:
 
 protected:
   /** Do the real retrieval of the Service. */
-  StatusCode retrieve( T*& service ) const override {
+  StatusCode retrieve( T*& service ) const override { return i_retrieve( service ); }
+
+  /// retrieve the service for ServiceHandles<ISomeInterfaces>
+  template <Gaudi::IsInterface I = T>
+  StatusCode i_retrieve( I*& service ) const {
     const ServiceLocatorHelper helper( *serviceLocator(), GaudiHandleBase::messageName(), this->parentName() );
-    return helper.getService( GaudiHandleBase::typeAndName(), true, T::interfaceID(), (void**)&service );
+    return helper.getService( GaudiHandleBase::typeAndName(), true, I::interfaceID(), (void**)&service );
+  }
+
+  /// retrieve the service for ServiceHandles<ActualService>
+  template <typename I = T>
+    requires( !Gaudi::IsInterface<I> )
+  StatusCode i_retrieve( I*& service ) const {
+    IService* svc = nullptr;
+    return i_retrieve( svc ).andThen( [&] {
+      service = dynamic_cast<I*>( svc );
+      if ( !service )
+        throw GaudiException( "unable to dcast Service " + this->typeAndName() + " to " +
+                                  System::typeinfoName( typeid( T ) ),
+                              this->typeAndName() + " retrieve", StatusCode::FAILURE );
+    } );
   }
 
   //   /** Do the real release of the Service */
diff --git a/GaudiTestSuite/CMakeLists.txt b/GaudiTestSuite/CMakeLists.txt
index f5d318ac9c..3207800958 100644
--- a/GaudiTestSuite/CMakeLists.txt
+++ b/GaudiTestSuite/CMakeLists.txt
@@ -108,6 +108,7 @@ gaudi_add_module(GaudiTestSuiteComponents
                          src/ToolHandles/FloatTool.cpp
                          src/NTuple/NTupleWriterProducers.cpp
                          src/NTuple/NTupleWriterImpls.cpp
+                         src/UseSvcWithoutInterface.cpp
                  LINK GaudiKernel
                       Gaudi::Functional
                       GaudiUtilsLib
diff --git a/GaudiTestSuite/src/UseSvcWithoutInterface.cpp b/GaudiTestSuite/src/UseSvcWithoutInterface.cpp
new file mode 100644
index 0000000000..3460ffa5cf
--- /dev/null
+++ b/GaudiTestSuite/src/UseSvcWithoutInterface.cpp
@@ -0,0 +1,45 @@
+/***********************************************************************************\
+* (c) Copyright 2025 CERN for the benefit of the LHCb and ATLAS collaborations      *
+*                                                                                   *
+* This software is distributed under the terms of the Apache version 2 licence,     *
+* copied verbatim in the file "LICENSE".                                            *
+*                                                                                   *
+* In applying this licence, CERN does not waive the privileges and immunities       *
+* granted to it by virtue of its status as an Intergovernmental Organization        *
+* or submit itself to any jurisdiction.                                             *
+\***********************************************************************************/
+#include <Gaudi/Algorithm.h>
+#include <Gaudi/Functional/Consumer.h>
+#include <GaudiKernel/Service.h>
+
+namespace Gaudi::TestSuite {
+  class SvcWithoutInterface : public Service {
+  public:
+    using Service::Service;
+
+    StatusCode initialize() override {
+      return Service::initialize().andThen( [this] { info() << "initialized" << endmsg; } );
+    }
+
+    void doSomething() const { info() << "doing something" << endmsg; }
+  };
+  DECLARE_COMPONENT( SvcWithoutInterface )
+
+  class UseSvcWithoutInterface : public Gaudi::Functional::Consumer<void()> {
+  public:
+    UseSvcWithoutInterface( const std::string& name, ISvcLocator* svcLoc )
+        : Consumer( name, svcLoc ), m_svc( "Gaudi::TestSuite::SvcWithoutInterface/SvcWithoutInterface", name ) {}
+    StatusCode initialize() override {
+      return Algorithm::initialize()
+          .andThen( [this] { info() << "initializing..." << endmsg; } )
+          .andThen( [this] { m_svc->doSomething(); } )
+          .andThen( [this] { info() << "initialized" << endmsg; } );
+    }
+    void operator()() const override {}
+
+  private:
+    // Add your member variables here
+    ServiceHandle<SvcWithoutInterface> m_svc;
+  };
+  DECLARE_COMPONENT( UseSvcWithoutInterface )
+} // namespace Gaudi::TestSuite
diff --git a/GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py b/GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py
new file mode 100644
index 0000000000..5a4c268c4b
--- /dev/null
+++ b/GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py
@@ -0,0 +1,53 @@
+#####################################################################################
+# (c) Copyright 2025 CERN for the benefit of the LHCb and ATLAS collaborations      #
+#                                                                                   #
+# This software is distributed under the terms of the Apache version 2 licence,     #
+# copied verbatim in the file "LICENSE".                                            #
+#                                                                                   #
+# In applying this licence, CERN does not waive the privileges and immunities       #
+# granted to it by virtue of its status as an Intergovernmental Organization        #
+# or submit itself to any jurisdiction.                                             #
+#####################################################################################
+
+from GaudiTesting import NO_ERROR_MESSAGES, GaudiExeTest
+
+
+def config():
+    from GaudiConfig2 import Configurables as C
+
+    conf = []
+    alg = C.Gaudi.TestSuite.UseSvcWithoutInterface("UseSvcWithoutInterface")
+    conf.append(alg)
+
+    appMgr = C.ApplicationMgr(
+        TopAlg=[alg],
+        EvtMax=1,
+        EvtSel="NONE",
+    )
+    conf.append(appMgr)
+
+    msgsvc = C.MessageSvc(Format="% F%25W%S%7W%R%T %0W%M")
+    conf.append(msgsvc)
+
+    return conf
+
+
+class Test(GaudiExeTest):
+    command = ["gaudirun.py", f"{__file__}:config"]
+
+    reference = {"messages_count": NO_ERROR_MESSAGES}
+
+    def test_stdout(self, stdout: bytes):
+        extracted_lines = [
+            line.split(None, 2)
+            for line in stdout.splitlines()
+            if line.startswith(b"UseSvcWithoutInterface")
+            or line.startswith(b"SvcWithoutInterface")
+        ]
+        expected_lines = [
+            [b"UseSvcWithoutInterface", b"INFO", b"initializing..."],
+            [b"SvcWithoutInterface", b"INFO", b"initialized"],
+            [b"SvcWithoutInterface", b"INFO", b"doing something"],
+            [b"UseSvcWithoutInterface", b"INFO", b"initialized"],
+        ]
+        assert extracted_lines == expected_lines
-- 
GitLab