diff --git a/GaudiAlg/GaudiAlg/Consumer.h b/GaudiAlg/GaudiAlg/Consumer.h
index c6540f9224d0551759e3d89f1930f39edf12eaff..3069c024b99abe0174a9b97688d274ce935f2010 100644
--- a/GaudiAlg/GaudiAlg/Consumer.h
+++ b/GaudiAlg/GaudiAlg/Consumer.h
@@ -5,14 +5,15 @@
 #include "GaudiAlg/FunctionalUtilities.h"
 #include <utility>
 
-namespace Gaudi {
-  namespace Functional {
+namespace Gaudi::Functional {
 
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
+  namespace details {
+
+    template <typename Signature, typename Traits_, bool isLegacy>
     class Consumer;
 
     template <typename... In, typename Traits_>
-    class Consumer<void( const In&... ), Traits_>
+    class Consumer<void( const In&... ), Traits_, true>
         : public details::DataHandleMixin<void, details::filter_evtcontext<In...>, Traits_> {
     public:
       using details::DataHandleMixin<void, details::filter_evtcontext<In...>, Traits_>::DataHandleMixin;
@@ -31,7 +32,33 @@ namespace Gaudi {
       // ... instead, they must implement the following operator
       virtual void operator()( const In&... ) const = 0;
     };
-  } // namespace Functional
-} // namespace Gaudi
+
+    template <typename... In, typename Traits_>
+    class Consumer<void( const In&... ), Traits_, false>
+        : public details::DataHandleMixin<void, details::filter_evtcontext<In...>, Traits_> {
+    public:
+      using details::DataHandleMixin<void, details::filter_evtcontext<In...>, Traits_>::DataHandleMixin;
+
+      // derived classes are NOT allowed to implement execute ...
+      StatusCode execute( const EventContext& ctx ) const override final {
+        try {
+          details::filter_evtcontext_t<In...>::apply( *this, ctx, this->m_inputs );
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+        return StatusCode::SUCCESS;
+      }
+
+      // ... instead, they must implement the following operator
+      virtual void operator()( const In&... ) const = 0;
+    };
+
+  } // namespace details
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using Consumer = details::Consumer<Signature, Traits_, details::isLegacy<Traits_>>;
+
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/FilterPredicate.h b/GaudiAlg/GaudiAlg/FilterPredicate.h
index 21110bb23992f0805765444eab244eb2a0a98b6c..14d64757b37bbc063623c3a8edcb745445db15ea 100644
--- a/GaudiAlg/GaudiAlg/FilterPredicate.h
+++ b/GaudiAlg/GaudiAlg/FilterPredicate.h
@@ -6,14 +6,15 @@
 #include <type_traits>
 #include <utility>
 
-namespace Gaudi {
-  namespace Functional {
+namespace Gaudi ::Functional {
 
-    template <typename T, typename Traits_ = Traits::useDefaults>
+  namespace details {
+
+    template <typename T, typename Traits_, bool isLegacy>
     class FilterPredicate;
 
     template <typename... In, typename Traits_>
-    class FilterPredicate<bool( const In&... ), Traits_>
+    class FilterPredicate<bool( const In&... ), Traits_, true>
         : public details::DataHandleMixin<void, std::tuple<In...>, Traits_> {
     public:
       using details::DataHandleMixin<void, std::tuple<In...>, Traits_>::DataHandleMixin;
@@ -32,7 +33,34 @@ namespace Gaudi {
       // ... instead, they must implement the following operator
       virtual bool operator()( const In&... ) const = 0;
     };
-  } // namespace Functional
-} // namespace Gaudi
+
+    template <typename... In, typename Traits_>
+    class FilterPredicate<bool( const In&... ), Traits_, false>
+        : public details::DataHandleMixin<void, std::tuple<In...>, Traits_> {
+    public:
+      using details::DataHandleMixin<void, std::tuple<In...>, Traits_>::DataHandleMixin;
+
+      // derived classes are NOT allowed to implement execute ...
+      StatusCode execute( const EventContext& ctx ) const override final {
+        try {
+          this->execState( ctx ).setFilterPassed(
+              details::filter_evtcontext_t<In...>::apply( *this, ctx, this->m_inputs ) );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // ... instead, they must implement the following operator
+      virtual bool operator()( const In&... ) const = 0;
+    };
+
+  } // namespace details
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using FilterPredicate = details::FilterPredicate<Signature, Traits_, details::isLegacy<Traits_>>;
+
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/FunctionalDetails.h b/GaudiAlg/GaudiAlg/FunctionalDetails.h
index b2666cf1c975485dbfba984ebd3657e0f10812ae..b7ea480c5c9f4203ae4494a2866d26235e0dc0c8 100644
--- a/GaudiAlg/GaudiAlg/FunctionalDetails.h
+++ b/GaudiAlg/GaudiAlg/FunctionalDetails.h
@@ -14,542 +14,560 @@
 #include "GaudiKernel/ThreadLocalContext.h"
 #include "GaudiKernel/detected.h"
 
-// Boost
-#include "boost/optional.hpp"
-
 // Range V3
 #include <range/v3/view/const.hpp>
 #include <range/v3/view/zip.hpp>
 
-namespace Gaudi {
-  namespace Functional {
-    namespace details {
-
-      // CRJ : Stuff for zipping
-      namespace zip {
-
-        /// Print the parameter
-        template <typename OS, typename Arg>
-        void printSizes( OS& out, Arg&& arg ) {
-          out << "SizeOf'" << System::typeinfoName( typeid( Arg ) ) << "'=" << std::forward<Arg>( arg ).size();
-        }
-
-        /// Print the parameters
-        template <typename OS, typename Arg, typename... Args>
-        void printSizes( OS& out, Arg&& arg, Args&&... args ) {
-          printSizes( out, arg );
-          out << ", ";
-          printSizes( out, args... );
-        }
-
-        /// Resolve case there is only one container in the range
-        template <typename A>
-        inline bool check_sizes( const A& ) noexcept {
-          return true;
-        }
-
-        /// Compare sizes of two containers
-        template <typename A, typename B>
-        inline bool check_sizes( const A& a, const B& b ) noexcept {
-          return a.size() == b.size();
-        }
-
-        /// Compare sizes of 3 or more containers
-        template <typename A, typename B, typename... C>
-        inline bool check_sizes( const A& a, const B& b, const C&... c ) noexcept {
-          return ( check_sizes( a, b ) && check_sizes( b, c... ) );
-        }
-
-        /// Verify the data container sizes have the same sizes
-        template <typename... Args>
-        inline decltype( auto ) verifySizes( Args&... args ) {
-          if ( UNLIKELY( !check_sizes( args... ) ) ) {
-            std::ostringstream mess;
-            mess << "Zipped containers have different sizes : ";
-            printSizes( mess, args... );
-            throw GaudiException( mess.str(), "Gaudi::Functional::details::zip::verifySizes", StatusCode::FAILURE );
-          }
-        }
-
-        /// Zips multiple containers together to form a single range
-        template <typename... Args>
-        inline decltype( auto ) range( Args&&... args ) {
+namespace Gaudi::Functional::details {
+
+  // CRJ : Stuff for zipping
+  namespace zip {
+
+    /// Print the parameter
+    template <typename OS, typename Arg>
+    void printSizes( OS& out, Arg&& arg ) {
+      out << "SizeOf'" << System::typeinfoName( typeid( Arg ) ) << "'=" << std::forward<Arg>( arg ).size();
+    }
+
+    /// Print the parameters
+    template <typename OS, typename Arg, typename... Args>
+    void printSizes( OS& out, Arg&& arg, Args&&... args ) {
+      printSizes( out, arg );
+      out << ", ";
+      printSizes( out, args... );
+    }
+
+    /// Resolve case there is only one container in the range
+    template <typename A>
+    inline bool check_sizes( const A& ) noexcept {
+      return true;
+    }
+
+    /// Compare sizes of two containers
+    template <typename A, typename B>
+    inline bool check_sizes( const A& a, const B& b ) noexcept {
+      return a.size() == b.size();
+    }
+
+    /// Compare sizes of 3 or more containers
+    template <typename A, typename B, typename... C>
+    inline bool check_sizes( const A& a, const B& b, const C&... c ) noexcept {
+      return ( check_sizes( a, b ) && check_sizes( b, c... ) );
+    }
+
+    /// Verify the data container sizes have the same sizes
+    template <typename... Args>
+    inline decltype( auto ) verifySizes( Args&... args ) {
+      if ( UNLIKELY( !check_sizes( args... ) ) ) {
+        std::ostringstream mess;
+        mess << "Zipped containers have different sizes : ";
+        printSizes( mess, args... );
+        throw GaudiException( mess.str(), "Gaudi::Functional::details::zip::verifySizes", StatusCode::FAILURE );
+      }
+    }
+
+    /// Zips multiple containers together to form a single range
+    template <typename... Args>
+    inline decltype( auto ) range( Args&&... args ) {
 #ifndef NDEBUG
-          verifySizes( args... );
+      verifySizes( args... );
 #endif
-          return ranges::view::zip( std::forward<Args>( args )... );
-        }
+      return ranges::view::zip( std::forward<Args>( args )... );
+    }
 
-        /// Zips multiple containers together to form a single const range
-        template <typename... Args>
-        inline decltype( auto ) const_range( Args&&... args ) {
+    /// Zips multiple containers together to form a single const range
+    template <typename... Args>
+    inline decltype( auto ) const_range( Args&&... args ) {
 #ifndef NDEBUG
-          verifySizes( args... );
+      verifySizes( args... );
 #endif
-          return ranges::view::const_( ranges::view::zip( std::forward<Args>( args )... ) );
-        }
-      } // namespace zip
-
-      using std::as_const;
-      using std::disjunction;
-      /////////////////////////////////////////
-      namespace details2 {
-        // note: boost::optional in boost 1.66 does not have 'has_value()'...
-        // that requires boost 1.68 or later... so for now, use operator bool() instead ;-(
-        template <typename T>
-        using is_optional_ = decltype( bool( std::declval<T>() ), std::declval<T>().value() );
-      } // namespace details2
-      template <typename Arg>
-      using is_optional = typename Gaudi::cpp17::is_detected<details2::is_optional_, Arg>;
-
-      template <typename Arg>
-      using require_is_optional = std::enable_if_t<is_optional<Arg>::value>;
-
-      template <typename Arg>
-      using require_is_not_optional = std::enable_if_t<!is_optional<Arg>::value>;
-
-      namespace details2 {
-        template <typename T, typename = void>
-        struct remove_optional {
-          using type = T;
-        };
-
-        template <typename T>
-        struct remove_optional<T, std::enable_if_t<is_optional<T>::value>> {
-          using type = typename T::value_type;
-        };
-      } // namespace details2
-
-      template <typename T>
-      using remove_optional_t = typename details2::remove_optional<T>::type;
-
-      constexpr struct invoke_optionally_t {
-        template <typename F, typename Arg, typename = require_is_not_optional<Arg>>
-        decltype( auto ) operator()( F&& f, Arg&& arg ) const {
-          return std::invoke( std::forward<F>( f ), std::forward<Arg>( arg ) );
-        }
-        template <typename F, typename Arg, typename = require_is_optional<Arg>>
-        void operator()( F&& f, Arg&& arg ) const {
-          if ( arg ) std::invoke( std::forward<F>( f ), *std::forward<Arg>( arg ) );
-        }
-      } invoke_optionally{};
-      /////////////////////////////////////////
-
-      template <typename Out1, typename Out2,
-                typename = std::enable_if_t<std::is_constructible<Out1, Out2>::value &&
-                                            std::is_base_of<DataObject, Out1>::value>>
-      Out1* put( DataObjectHandle<Out1>& out_handle, Out2&& out ) {
-        return out_handle.put( std::make_unique<Out1>( std::forward<Out2>( out ) ) );
+      return ranges::view::const_( ranges::view::zip( std::forward<Args>( args )... ) );
+    }
+  } // namespace zip
+
+  /////////////////////////////////////////
+  namespace details2 {
+    // note: boost::optional in boost 1.66 does not have 'has_value()'...
+    // that requires boost 1.68 or later... so for now, use operator bool() instead ;-(
+    template <typename T>
+    using is_optional_ = decltype( bool( std::declval<T>() ), std::declval<T>().value() );
+  } // namespace details2
+  template <typename Arg>
+  using is_optional = typename Gaudi::cpp17::is_detected<details2::is_optional_, Arg>;
+
+  template <typename Arg>
+  using require_is_optional = std::enable_if_t<is_optional<Arg>::value>;
+
+  template <typename Arg>
+  using require_is_not_optional = std::enable_if_t<!is_optional<Arg>::value>;
+
+  namespace details2 {
+    template <typename T, typename = void>
+    struct remove_optional {
+      using type = T;
+    };
+
+    template <typename T>
+    struct remove_optional<T, std::enable_if_t<is_optional<T>::value>> {
+      using type = typename T::value_type;
+    };
+  } // namespace details2
+
+  template <typename T>
+  using remove_optional_t = typename details2::remove_optional<T>::type;
+
+  constexpr struct invoke_optionally_t {
+    template <typename F, typename Arg, typename = require_is_not_optional<Arg>>
+    decltype( auto ) operator()( F&& f, Arg&& arg ) const {
+      return std::invoke( std::forward<F>( f ), std::forward<Arg>( arg ) );
+    }
+    template <typename F, typename Arg, typename = require_is_optional<Arg>>
+    void operator()( F&& f, Arg&& arg ) const {
+      if ( arg ) std::invoke( std::forward<F>( f ), *std::forward<Arg>( arg ) );
+    }
+  } invoke_optionally{};
+  /////////////////////////////////////////
+
+  template <
+      typename Out1, typename Out2,
+      typename = std::enable_if_t<std::is_constructible<Out1, Out2>::value && std::is_base_of<DataObject, Out1>::value>>
+  Out1* put( const DataObjectHandle<Out1>& out_handle, Out2&& out ) {
+    return out_handle.put( std::make_unique<Out1>( std::forward<Out2>( out ) ) );
+  }
+
+  template <typename Out1, typename Out2, typename = std::enable_if_t<std::is_constructible<Out1, Out2>::value>>
+  void put( const DataObjectHandle<AnyDataWrapper<Out1>>& out_handle, Out2&& out ) {
+    out_handle.put( std::forward<Out2>( out ) );
+  }
+
+  // optional put
+  template <typename OutHandle, typename OptOut, typename = require_is_optional<OptOut>>
+  void put( const OutHandle& out_handle, OptOut&& out ) {
+    if ( out ) put( out_handle, *std::forward<OptOut>( out ) );
+  }
+  /////////////////////////////////////////
+  // adapt to differences between eg. std::vector (which has push_back) and KeyedContainer (which has insert)
+  // adapt to getting a T, and a container wanting T* by doing new T{ std::move(out) }
+  // adapt to getting a optional<T>
+
+  constexpr struct insert_t {
+    // for Container<T*>, return T
+    template <typename Container>
+    using c_remove_ptr_t = std::remove_pointer_t<typename Container::value_type>;
+
+    template <typename Container, typename Value>
+    auto operator()( Container& c, Value&& v ) const -> decltype( c.push_back( v ) ) {
+      return c.push_back( std::forward<Value>( v ) );
+    }
+
+    template <typename Container, typename Value>
+    auto operator()( Container& c, Value&& v ) const -> decltype( c.insert( v ) ) {
+      return c.insert( std::forward<Value>( v ) );
+    }
+
+    // Container<T*> with T&& as argument
+    template <typename Container, typename = std::enable_if_t<std::is_pointer<typename Container::value_type>::value>>
+    auto operator()( Container& c, c_remove_ptr_t<Container>&& v ) const {
+      return operator()( c, new c_remove_ptr_t<Container>{std::move( v )} );
+    }
+
+  } insert{};
+
+  /////////////////////////////////////////
+
+  constexpr struct deref_t {
+    template <typename In, typename = std::enable_if_t<!std::is_pointer<In>::value>>
+    const In& operator()( const In& in ) const {
+      return in;
+    }
+
+    template <typename In>
+    const In& operator()( const In* in ) const {
+      assert( in != nullptr );
+      return *in;
+    }
+  } deref{};
+
+  /////////////////////////////////////////
+  // if Container is a pointer, then we're optional items
+  namespace details2 {
+    template <typename Container, typename Value>
+    void push_back( Container& c, const Value& v, std::true_type ) {
+      c.push_back( v );
+    }
+    template <typename Container, typename Value>
+    void push_back( Container& c, const Value& v, std::false_type ) {
+      c.push_back( &v );
+    }
+
+    template <typename In>
+    struct get_from_handle {
+      template <template <typename> class Handle, typename I,
+                typename = std::enable_if_t<std::is_convertible<I, In>::value>>
+      auto operator()( const Handle<I>& h ) -> const In& {
+        return *h.get();
       }
-
-      template <typename Out1, typename Out2, typename = std::enable_if_t<std::is_constructible<Out1, Out2>::value>>
-      void put( DataObjectHandle<AnyDataWrapper<Out1>>& out_handle, Out2&& out ) {
-        out_handle.put( std::forward<Out2>( out ) );
-      }
-
-      // optional put
-      template <typename OutHandle, typename OptOut, typename = require_is_optional<OptOut>>
-      void put( OutHandle& out_handle, OptOut&& out ) {
-        if ( out ) put( out_handle, *std::forward<OptOut>( out ) );
+      template <template <typename> class Handle, typename I,
+                typename = std::enable_if_t<std::is_convertible<I*, In>::value>>
+      auto operator()( const Handle<I>& h ) -> const In {
+        return h.getIfExists();
+      } // In is-a pointer
+    };
+
+    template <typename T>
+    T* deref_if( T* const t, std::false_type ) {
+      return t;
+    }
+    template <typename T>
+    T& deref_if( T* const t, std::true_type ) {
+      return *t;
+    }
+  } // namespace details2
+
+  template <typename Container>
+  class vector_of_const_ {
+    static constexpr bool is_pointer = std::is_pointer<Container>::value;
+    using val_t                      = std::add_const_t<std::remove_pointer_t<Container>>;
+    using ptr_t                      = std::add_pointer_t<val_t>;
+    using ref_t                      = std::add_lvalue_reference_t<val_t>;
+    using ContainerVector            = std::vector<ptr_t>;
+    ContainerVector m_containers;
+
+  public:
+    using value_type = std::conditional_t<is_pointer, ptr_t, val_t>;
+    using size_type  = typename ContainerVector::size_type;
+    class iterator {
+      using it_t = typename ContainerVector::const_iterator;
+      it_t m_i;
+      friend class vector_of_const_;
+      iterator( it_t iter ) : m_i( iter ) {}
+      using ret_t = std::conditional_t<is_pointer, ptr_t, ref_t>;
+
+    public:
+      using iterator_category = typename it_t::iterator_category;
+      using value_type        = typename it_t::iterator_category;
+      using reference         = typename it_t::reference;
+      using pointer           = typename it_t::pointer;
+      using difference_type   = typename it_t::difference_type;
+
+      friend bool operator!=( const iterator& lhs, const iterator& rhs ) { return lhs.m_i != rhs.m_i; }
+      friend bool operator==( const iterator& lhs, const iterator& rhs ) { return lhs.m_i == rhs.m_i; }
+      friend auto operator-( const iterator& lhs, const iterator& rhs ) { return lhs.m_i - rhs.m_i; }
+      ret_t       operator*() const { return details2::deref_if( *m_i, std::integral_constant<bool, !is_pointer>{} ); }
+      iterator&   operator++() {
+        ++m_i;
+        return *this;
       }
-      /////////////////////////////////////////
-      // adapt to differences between eg. std::vector (which has push_back) and KeyedContainer (which has insert)
-      // adapt to getting a T, and a container wanting T* by doing new T{ std::move(out) }
-      // adapt to getting a optional<T>
-
-      constexpr struct insert_t {
-        // for Container<T*>, return T
-        template <typename Container>
-        using c_remove_ptr_t = std::remove_pointer_t<typename Container::value_type>;
-
-        template <typename Container, typename Value>
-        auto operator()( Container& c, Value&& v ) const -> decltype( c.push_back( v ) ) {
-          return c.push_back( std::forward<Value>( v ) );
-        }
-
-        template <typename Container, typename Value>
-        auto operator()( Container& c, Value&& v ) const -> decltype( c.insert( v ) ) {
-          return c.insert( std::forward<Value>( v ) );
-        }
-
-        // Container<T*> with T&& as argument
-        template <typename Container,
-                  typename = std::enable_if_t<std::is_pointer<typename Container::value_type>::value>>
-        auto operator()( Container& c, c_remove_ptr_t<Container>&& v ) const {
-          return operator()( c, new c_remove_ptr_t<Container>{std::move( v )} );
-        }
-
-      } insert{};
-
-      /////////////////////////////////////////
-
-      constexpr struct deref_t {
-        template <typename In, typename = std::enable_if_t<!std::is_pointer<In>::value>>
-        const In& operator()( const In& in ) const {
-          return in;
-        }
-
-        template <typename In>
-        const In& operator()( const In* in ) const {
-          assert( in != nullptr );
-          return *in;
-        }
-      } deref{};
-
-      /////////////////////////////////////////
-      // if Container is a pointer, then we're optional items
-      namespace details2 {
-        template <typename Container, typename Value>
-        void push_back( Container& c, const Value& v, std::true_type ) {
-          c.push_back( v );
-        }
-        template <typename Container, typename Value>
-        void push_back( Container& c, const Value& v, std::false_type ) {
-          c.push_back( &v );
-        }
-
-        template <typename In>
-        struct get_from_handle {
-          template <template <typename> class Handle, typename I,
-                    typename = std::enable_if_t<std::is_convertible<I, In>::value>>
-          auto operator()( const Handle<I>& h ) -> const In& {
-            return *h.get();
-          }
-          template <template <typename> class Handle, typename I,
-                    typename = std::enable_if_t<std::is_convertible<I*, In>::value>>
-          auto operator()( const Handle<I>& h ) -> const In {
-            return h.getIfExists();
-          } // In is-a pointer
-        };
-
-        template <typename T>
-        T* deref_if( T* const t, std::false_type ) {
-          return t;
-        }
-        template <typename T>
-        T& deref_if( T* const t, std::true_type ) {
-          return *t;
-        }
-      } // namespace details2
-
-      template <typename Container>
-      class vector_of_const_ {
-        static constexpr bool is_pointer = std::is_pointer<Container>::value;
-        using val_t                      = std::add_const_t<std::remove_pointer_t<Container>>;
-        using ptr_t                      = std::add_pointer_t<val_t>;
-        using ref_t                      = std::add_lvalue_reference_t<val_t>;
-        using ContainerVector            = std::vector<ptr_t>;
-        ContainerVector m_containers;
-
-      public:
-        using value_type = std::conditional_t<is_pointer, ptr_t, val_t>;
-        using size_type  = typename ContainerVector::size_type;
-        class iterator {
-          using it_t = typename ContainerVector::const_iterator;
-          it_t m_i;
-          friend class vector_of_const_;
-          iterator( it_t iter ) : m_i( iter ) {}
-          using ret_t = std::conditional_t<is_pointer, ptr_t, ref_t>;
-
-        public:
-          using iterator_category = typename it_t::iterator_category;
-          using value_type        = typename it_t::iterator_category;
-          using reference         = typename it_t::reference;
-          using pointer           = typename it_t::pointer;
-          using difference_type   = typename it_t::difference_type;
-
-          friend bool operator!=( const iterator& lhs, const iterator& rhs ) { return lhs.m_i != rhs.m_i; }
-          friend bool operator==( const iterator& lhs, const iterator& rhs ) { return lhs.m_i == rhs.m_i; }
-          friend auto operator-( const iterator& lhs, const iterator& rhs ) { return lhs.m_i - rhs.m_i; }
-          ret_t operator*() const { return details2::deref_if( *m_i, std::integral_constant<bool, !is_pointer>{} ); }
-          iterator& operator++() {
-            ++m_i;
-            return *this;
-          }
-          iterator& operator--() {
-            --m_i;
-            return *this;
-          }
-          bool     is_null() const { return !*m_i; }
-          explicit operator bool() const { return !is_null(); }
-        };
-        vector_of_const_() = default;
-        void reserve( size_type size ) { m_containers.reserve( size ); }
-        template <typename T> // , typename = std::is_convertible<T,std::conditional_t<is_pointer,ptr_t,val_t>>
-        void push_back( T&& container ) {
-          details2::push_back( m_containers, std::forward<T>( container ), std::integral_constant<bool, is_pointer>{} );
-        } // note: does not copy its argument, so we're not really a container...
-        iterator  begin() const { return m_containers.begin(); }
-        iterator  end() const { return m_containers.end(); }
-        size_type size() const { return m_containers.size(); }
-
-        template <typename X = Container>
-        std::enable_if_t<!std::is_pointer<X>::value, ref_t> operator[]( size_type i ) const {
-          return *m_containers[i];
-        }
-
-        template <typename X = Container>
-        std::enable_if_t<std::is_pointer<X>::value, ptr_t> operator[]( size_type i ) const {
-          return m_containers[i];
-        }
-
-        template <typename X = Container>
-        std::enable_if_t<!std::is_pointer<X>::value, ref_t> at( size_type i ) const {
-          return *m_containers[i];
-        }
-
-        template <typename X = Container>
-        std::enable_if_t<std::is_pointer<X>::value, ptr_t> at( size_type i ) const {
-          return m_containers[i];
-        }
-
-        bool is_null( size_type i ) const { return !m_containers[i]; }
-      };
-
-      /////////////////////////////////////////
-      namespace detail2 { // utilities for detected_or_t{,_} usage
-        template <typename Tr>
-        using BaseClass_t = typename Tr::BaseClass;
-        template <typename Tr, typename T>
-        using OutputHandle_t = typename Tr::template OutputHandle<T>;
-        template <typename Tr, typename T>
-        using InputHandle_t = typename Tr::template InputHandle<T>;
-      } // namespace detail2
-
-      // check whether Traits::BaseClass is a valid type,
-      // if so, define BaseClass_t<Traits> as being Traits::BaseClass
-      // else   define                     as being GaudiAlgorithm
-      template <typename Tr>
-      using BaseClass_t = Gaudi::cpp17::detected_or_t<GaudiAlgorithm, detail2::BaseClass_t, Tr>;
-
-      // check whether Traits::{Input,Output}Handle<T> is a valid type,
-      // if so, define {Input,Output}Handle_t<Traits,T> as being Traits::{Input,Output}Handle<T>
-      // else   define                                  as being DataObject{Read,,Write}Handle<T>
-      template <typename Tr, typename T>
-      using OutputHandle_t = Gaudi::cpp17::detected_or_t<DataObjectWriteHandle<T>, detail2::OutputHandle_t, Tr, T>;
-      template <typename Tr, typename T>
-      using InputHandle_t = Gaudi::cpp17::detected_or_t<DataObjectReadHandle<T>, detail2::InputHandle_t, Tr, T>;
-
-      /////////
-
-      template <typename Handles>
-      Handles make_vector_of_handles( IDataHandleHolder* owner, const std::vector<std::string>& init ) {
-        Handles handles;
-        handles.reserve( init.size() );
-        std::transform( init.begin(), init.end(),
-                        std::back_inserter( handles ), [&]( const std::string& loc ) -> typename Handles::value_type {
-                          return {loc, owner};
-                        } );
-        return handles;
+      iterator& operator--() {
+        --m_i;
+        return *this;
       }
-
-      ///////////////////////
-      // given a pack, return a corresponding tuple
-      template <typename... In>
-      struct filter_evtcontext_t {
-        using type = std::tuple<In...>;
-
-        static_assert( !details::disjunction<std::is_same<EventContext, In>...>::value,
-                       "EventContext can only appear as first argument" );
-
-        template <typename Algorithm, typename Handles>
-        static auto apply( const Algorithm& algo, Handles& handles ) {
-          return std::apply( [&]( const auto&... handle ) { return algo( details::deref( handle.get() )... ); },
-                             handles );
-        }
-      };
-
-      // except when it starts with EventContext, then drop it
-      template <typename... In>
-      struct filter_evtcontext_t<EventContext, In...> {
-        using type = std::tuple<In...>;
-
-        static_assert( !details::disjunction<std::is_same<EventContext, In>...>::value,
-                       "EventContext can only appear as first argument" );
-
-        template <typename Algorithm, typename Handles>
-        static auto apply( const Algorithm& algo, Handles& handles ) {
-          return std::apply(
-              [&]( const auto&... handle ) {
-                return algo( Gaudi::Hive::currentContext(), details::deref( handle.get() )... );
-              },
-              handles );
-        }
-      };
-
-      template <typename... In>
-      using filter_evtcontext = typename filter_evtcontext_t<In...>::type;
-
-      template <typename OutputSpec, typename InputSpec, typename Traits_>
-      class DataHandleMixin;
-
-      template <typename... Out, typename... In, typename Traits_>
-      class DataHandleMixin<std::tuple<Out...>, std::tuple<In...>, Traits_> : public BaseClass_t<Traits_> {
-        static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value,
-                       "BaseClass must inherit from Algorithm" );
-
-        template <typename IArgs, typename OArgs, std::size_t... I, std::size_t... J>
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const IArgs& inputs,
-                         std::index_sequence<I...>, const OArgs& outputs, std::index_sequence<J...> )
-            : BaseClass_t<Traits_>( name, pSvcLocator )
-            , m_inputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<I>( inputs ) )... )
-            , m_outputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<J>( outputs ) )... ) {
-          // make sure this algorithm is seen as reentrant by Gaudi
-          this->setProperty( "Cardinality", 0 );
-        }
-
-      public:
-        using KeyValue                     = std::pair<std::string, std::string>;
-        constexpr static std::size_t N_in  = sizeof...( In );
-        constexpr static std::size_t N_out = sizeof...( Out );
-
-        // generic constructor:  N -> M
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N_in>& inputs,
-                         const std::array<KeyValue, N_out>& outputs )
-            : DataHandleMixin( name, pSvcLocator, inputs, std::index_sequence_for<In...>{}, outputs,
-                               std::index_sequence_for<Out...>{} ) {}
-
-        // special cases: forward to the generic case...
-        // 1 -> 1
-        DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& input, const KeyValue& output )
-            : DataHandleMixin( name, locator, std::array<KeyValue, 1>{input}, std::array<KeyValue, 1>{output} ) {}
-        // 1 -> N
-        DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& input,
-                         const std::array<KeyValue, N_out>& outputs )
-            : DataHandleMixin( name, locator, std::array<KeyValue, 1>{input}, outputs ) {}
-        // N -> 1
-        DataHandleMixin( const std::string& name, ISvcLocator* locator, const std::array<KeyValue, N_in>& inputs,
-                         const KeyValue& output )
-            : DataHandleMixin( name, locator, inputs, std::array<KeyValue, 1>{output} ) {}
-
-        template <std::size_t N = 0>
-        const std::string& inputLocation() const {
-          return std::get<N>( m_inputs ).objKey();
-        }
-        constexpr unsigned int inputLocationSize() const { return N_in; }
-
-        template <std::size_t N = 0>
-        const std::string& outputLocation() const {
-          return std::get<N>( m_outputs ).objKey();
-        }
-        constexpr unsigned int outputLocationSize() const { return N_out; }
-
-      protected:
-        bool isReEntrant() const override { return true; }
-
-        std::tuple<details::InputHandle_t<Traits_, In>...>   m_inputs;
-        std::tuple<details::OutputHandle_t<Traits_, Out>...> m_outputs;
-      };
-
-      template <typename Traits_>
-      class DataHandleMixin<void, std::tuple<>, Traits_> : public BaseClass_t<Traits_> {
-        static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value,
-                       "BaseClass must inherit from Algorithm" );
-
-      public:
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator )
-            : BaseClass_t<Traits_>( name, pSvcLocator ) {
-          // make sure this algorithm is seen as reentrant by Gaudi
-          this->setProperty( "Cardinality", 0 );
-        }
-
-      protected:
-        bool isReEntrant() const override { return true; }
-
-        std::tuple<> m_inputs;
-      };
-
-      template <typename... In, typename Traits_>
-      class DataHandleMixin<void, std::tuple<In...>, Traits_> : public BaseClass_t<Traits_> {
-        static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value,
-                       "BaseClass must inherit from Algorithm" );
-
-        template <typename IArgs, std::size_t... I>
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const IArgs& inputs,
-                         std::index_sequence<I...> )
-            : BaseClass_t<Traits_>( name, pSvcLocator )
-            , m_inputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<I>( inputs ) )... ) {
-          // make sure this algorithm is seen as reentrant by Gaudi
-          this->setProperty( "Cardinality", 0 );
-        }
-
-      public:
-        using KeyValue                    = std::pair<std::string, std::string>;
-        constexpr static std::size_t N_in = sizeof...( In );
-
-        // generic constructor:  N -> 0
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N_in>& inputs )
-            : DataHandleMixin( name, pSvcLocator, inputs, std::index_sequence_for<In...>{} ) {}
-
-        // special cases: forward to the generic case...
-        // 1 -> 0
-        DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& input )
-            : DataHandleMixin( name, locator, std::array<KeyValue, 1>{input} ) {}
-
-        template <std::size_t N = 0>
-        const std::string& inputLocation() const {
-          return std::get<N>( m_inputs ).objKey();
-        }
-        constexpr unsigned int inputLocationSize() const { return N_in; }
-
-      protected:
-        bool isReEntrant() const override { return true; }
-
-        std::tuple<details::InputHandle_t<Traits_, In>...> m_inputs;
-      };
-
-      template <typename... Out, typename Traits_>
-      class DataHandleMixin<std::tuple<Out...>, void, Traits_> : public BaseClass_t<Traits_> {
-        static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value,
-                       "BaseClass must inherit from Algorithm" );
-
-        template <typename OArgs, std::size_t... J>
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const OArgs& outputs,
-                         std::index_sequence<J...> )
-            : BaseClass_t<Traits_>( name, pSvcLocator )
-            , m_outputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<J>( outputs ) )... ) {
-          // make sure this algorithm is seen as reentrant by Gaudi
-          this->setProperty( "Cardinality", 0 );
-        }
-
-      public:
-        using KeyValue                     = std::pair<std::string, std::string>;
-        constexpr static std::size_t N_out = sizeof...( Out );
-
-        // generic constructor:  0 -> N
-        DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N_out>& outputs )
-            : DataHandleMixin( name, pSvcLocator, outputs, std::index_sequence_for<Out...>{} ) {}
-
-        // 0 -> 1
-        DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& output )
-            : DataHandleMixin( name, locator, std::array<KeyValue, 1>{output} ) {}
-
-        template <std::size_t N = 0>
-        const std::string& outputLocation() const {
-          return std::get<N>( m_outputs ).objKey();
-        }
-        constexpr unsigned int outputLocationSize() const { return N_out; }
-
-      protected:
-        bool isReEntrant() const override { return true; }
-
-        std::tuple<details::OutputHandle_t<Traits_, Out>...> m_outputs;
-      };
-
-      /////////////////
-      template <typename Fun, typename Container, typename... Args>
-      constexpr void applyPostProcessing( const Fun&, Container&, Args... ) {
-        static_assert( sizeof...( Args ) == 0, "Args should not be used!" );
-      }
-
-      template <typename Fun, typename Container>
-      auto applyPostProcessing( const Fun& fun, Container& c ) -> decltype( fun.postprocess( c ), void() ) {
-        fun.postprocess( c );
-      }
-
-      /////////////////
-    } // namespace details
-  }   // namespace Functional
-} // namespace Gaudi
+      bool     is_null() const { return !*m_i; }
+      explicit operator bool() const { return !is_null(); }
+    };
+    vector_of_const_() = default;
+    void reserve( size_type size ) { m_containers.reserve( size ); }
+    template <typename T> // , typename = std::is_convertible<T,std::conditional_t<is_pointer,ptr_t,val_t>>
+    void push_back( T&& container ) {
+      details2::push_back( m_containers, std::forward<T>( container ), std::integral_constant<bool, is_pointer>{} );
+    } // note: does not copy its argument, so we're not really a container...
+    iterator  begin() const { return m_containers.begin(); }
+    iterator  end() const { return m_containers.end(); }
+    size_type size() const { return m_containers.size(); }
+
+    template <typename X = Container>
+    std::enable_if_t<!std::is_pointer<X>::value, ref_t> operator[]( size_type i ) const {
+      return *m_containers[i];
+    }
+
+    template <typename X = Container>
+    std::enable_if_t<std::is_pointer<X>::value, ptr_t> operator[]( size_type i ) const {
+      return m_containers[i];
+    }
+
+    template <typename X = Container>
+    std::enable_if_t<!std::is_pointer<X>::value, ref_t> at( size_type i ) const {
+      return *m_containers[i];
+    }
+
+    template <typename X = Container>
+    std::enable_if_t<std::is_pointer<X>::value, ptr_t> at( size_type i ) const {
+      return m_containers[i];
+    }
+
+    bool is_null( size_type i ) const { return !m_containers[i]; }
+  };
+
+  /////////////////////////////////////////
+  namespace detail2 { // utilities for detected_or_t{,_} usage
+    template <typename Tr>
+    using BaseClass_t = typename Tr::BaseClass;
+    template <typename Tr, typename T>
+    using OutputHandle_t = typename Tr::template OutputHandle<T>;
+    template <typename Tr, typename T>
+    using InputHandle_t = typename Tr::template InputHandle<T>;
+  } // namespace detail2
+
+  // check whether Traits::BaseClass is a valid type,
+  // if so, define BaseClass_t<Traits> as being Traits::BaseClass
+  // else   define                     as being GaudiAlgorithm
+  template <typename Tr>
+  using BaseClass_t = Gaudi::cpp17::detected_or_t<GaudiAlgorithm, detail2::BaseClass_t, Tr>;
+
+  // check whether Traits::{Input,Output}Handle<T> is a valid type,
+  // if so, define {Input,Output}Handle_t<Traits,T> as being Traits::{Input,Output}Handle<T>
+  // else   define                                  as being DataObject{Read,,Write}Handle<T>
+  template <typename Tr, typename T>
+  using OutputHandle_t = Gaudi::cpp17::detected_or_t<DataObjectWriteHandle<T>, detail2::OutputHandle_t, Tr, T>;
+  template <typename Tr, typename T>
+  using InputHandle_t = Gaudi::cpp17::detected_or_t<DataObjectReadHandle<T>, detail2::InputHandle_t, Tr, T>;
+
+  template <typename Traits>
+  inline constexpr bool isLegacy =
+      std::is_base_of_v<Gaudi::details::LegacyAlgorithmAdapter, details::BaseClass_t<Traits>>;
+
+  /////////
+
+  template <typename Handles>
+  Handles make_vector_of_handles( IDataHandleHolder* owner, const std::vector<std::string>& init ) {
+    Handles handles;
+    handles.reserve( init.size() );
+    std::transform( init.begin(), init.end(),
+                    std::back_inserter( handles ), [&]( const std::string& loc ) -> typename Handles::value_type {
+                      return {loc, owner};
+                    } );
+    return handles;
+  }
+
+  ///////////////////////
+  // given a pack, return a corresponding tuple
+  template <typename... In>
+  struct filter_evtcontext_t {
+    using type = std::tuple<In...>;
+
+    static_assert( !std::disjunction<std::is_same<EventContext, In>...>::value,
+                   "EventContext can only appear as first argument" );
+
+    template <typename Algorithm, typename Handles>
+    static auto apply( const Algorithm& algo, Handles& handles ) {
+      return std::apply( [&]( const auto&... handle ) { return algo( details::deref( handle.get() )... ); }, handles );
+    }
+    template <typename Algorithm, typename Handles>
+    static auto apply( const Algorithm& algo, const EventContext&, Handles& handles ) {
+      return std::apply( [&]( const auto&... handle ) { return algo( details::deref( handle.get() )... ); }, handles );
+    }
+  };
+
+  // except when it starts with EventContext, then drop it
+  template <typename... In>
+  struct filter_evtcontext_t<EventContext, In...> {
+    using type = std::tuple<In...>;
+
+    static_assert( !std::disjunction<std::is_same<EventContext, In>...>::value,
+                   "EventContext can only appear as first argument" );
+
+    template <typename Algorithm, typename Handles>
+    static auto apply( const Algorithm& algo, Handles& handles ) {
+      return std::apply(
+          [&]( const auto&... handle ) {
+            return algo( Gaudi::Hive::currentContext(), details::deref( handle.get() )... );
+          },
+          handles );
+    }
+
+    template <typename Algorithm, typename Handles>
+    static auto apply( const Algorithm& algo, const EventContext& ctx, Handles& handles ) {
+      return std::apply( [&]( const auto&... handle ) { return algo( ctx, details::deref( handle.get() )... ); },
+                         handles );
+    }
+  };
+
+  template <typename... In>
+  using filter_evtcontext = typename filter_evtcontext_t<In...>::type;
+
+  template <typename OutputSpec, typename InputSpec, typename Traits_>
+  class DataHandleMixin;
+
+  template <typename Out, typename In, typename Tr>
+  void updateHandleLocation( DataHandleMixin<Out, In, Tr>& parent, const std::string& prop,
+                             const std::string& newLoc ) {
+    auto sc = parent.setProperty( prop, newLoc );
+    if ( sc.isFailure() ) throw GaudiException( "Could not set Property", prop + " -> " + newLoc, sc );
+  }
+
+  template <typename Out, typename In, typename Tr>
+  void updateHandleLocations( DataHandleMixin<Out, In, Tr>& parent, const std::string& prop,
+                              const std::vector<std::string>& newLocs ) {
+    std::ostringstream ss;
+    GaudiUtils::details::ostream_joiner( ss << '[', newLocs, ", ", []( std::ostream & os, const auto& i ) -> auto& {
+      return os << "'" << i << "'";
+    } ) << ']';
+    auto sc = parent.setProperty( prop, ss.str() );
+    if ( sc.isFailure() ) throw GaudiException( "Could not set Property", prop + " -> " + ss.str(), sc );
+  }
+
+  template <typename... Out, typename... In, typename Traits_>
+  class DataHandleMixin<std::tuple<Out...>, std::tuple<In...>, Traits_> : public BaseClass_t<Traits_> {
+    static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value, "BaseClass must inherit from Algorithm" );
+
+    template <typename IArgs, typename OArgs, std::size_t... I, std::size_t... J>
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const IArgs& inputs, std::index_sequence<I...>,
+                     const OArgs& outputs, std::index_sequence<J...> )
+        : BaseClass_t<Traits_>( name, pSvcLocator )
+        , m_inputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<I>( inputs ) )... )
+        , m_outputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<J>( outputs ) )... ) {
+      // make sure this algorithm is seen as reentrant by Gaudi
+      this->setProperty( "Cardinality", 0 );
+    }
+
+  public:
+    constexpr static std::size_t N_in  = sizeof...( In );
+    constexpr static std::size_t N_out = sizeof...( Out );
+
+    using KeyValue  = std::pair<std::string, std::string>;
+    using KeyValues = std::pair<std::string, std::vector<std::string>>;
+
+    // generic constructor:  N -> M
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N_in>& inputs,
+                     const std::array<KeyValue, N_out>& outputs )
+        : DataHandleMixin( name, pSvcLocator, inputs, std::index_sequence_for<In...>{}, outputs,
+                           std::index_sequence_for<Out...>{} ) {}
+
+    // special cases: forward to the generic case...
+    // 1 -> 1
+    DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& input, const KeyValue& output )
+        : DataHandleMixin( name, locator, std::array<KeyValue, 1>{input}, std::array<KeyValue, 1>{output} ) {}
+    // 1 -> N
+    DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& input,
+                     const std::array<KeyValue, N_out>& outputs )
+        : DataHandleMixin( name, locator, std::array<KeyValue, 1>{input}, outputs ) {}
+    // N -> 1
+    DataHandleMixin( const std::string& name, ISvcLocator* locator, const std::array<KeyValue, N_in>& inputs,
+                     const KeyValue& output )
+        : DataHandleMixin( name, locator, inputs, std::array<KeyValue, 1>{output} ) {}
+
+    template <std::size_t N = 0>
+    const std::string& inputLocation() const {
+      return std::get<N>( m_inputs ).objKey();
+    }
+    constexpr unsigned int inputLocationSize() const { return N_in; }
+
+    template <std::size_t N = 0>
+    const std::string& outputLocation() const {
+      return std::get<N>( m_outputs ).objKey();
+    }
+    constexpr unsigned int outputLocationSize() const { return N_out; }
+
+  protected:
+    bool isReEntrant() const override { return true; }
+
+    std::tuple<details::InputHandle_t<Traits_, In>...>   m_inputs;
+    std::tuple<details::OutputHandle_t<Traits_, Out>...> m_outputs;
+  };
+
+  template <typename Traits_>
+  class DataHandleMixin<void, std::tuple<>, Traits_> : public BaseClass_t<Traits_> {
+    static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value, "BaseClass must inherit from Algorithm" );
+
+  public:
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator ) : BaseClass_t<Traits_>( name, pSvcLocator ) {
+      // make sure this algorithm is seen as reentrant by Gaudi
+      this->setProperty( "Cardinality", 0 );
+    }
+
+  protected:
+    bool isReEntrant() const override { return true; }
+
+    std::tuple<> m_inputs;
+  };
+
+  template <typename... In, typename Traits_>
+  class DataHandleMixin<void, std::tuple<In...>, Traits_> : public BaseClass_t<Traits_> {
+    static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value, "BaseClass must inherit from Algorithm" );
+
+    template <typename IArgs, std::size_t... I>
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const IArgs& inputs, std::index_sequence<I...> )
+        : BaseClass_t<Traits_>( name, pSvcLocator )
+        , m_inputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<I>( inputs ) )... ) {
+      // make sure this algorithm is seen as reentrant by Gaudi
+      this->setProperty( "Cardinality", 0 );
+    }
+
+  public:
+    using KeyValue                    = std::pair<std::string, std::string>;
+    using KeyValues                   = std::pair<std::string, std::vector<std::string>>;
+    constexpr static std::size_t N_in = sizeof...( In );
+
+    // generic constructor:  N -> 0
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N_in>& inputs )
+        : DataHandleMixin( name, pSvcLocator, inputs, std::index_sequence_for<In...>{} ) {}
+
+    // special cases: forward to the generic case...
+    // 1 -> 0
+    DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& input )
+        : DataHandleMixin( name, locator, std::array<KeyValue, 1>{input} ) {}
+
+    template <std::size_t N = 0>
+    const std::string& inputLocation() const {
+      return std::get<N>( m_inputs ).objKey();
+    }
+    constexpr unsigned int inputLocationSize() const { return N_in; }
+
+  protected:
+    bool isReEntrant() const override { return true; }
+
+    std::tuple<details::InputHandle_t<Traits_, In>...> m_inputs;
+  };
+
+  template <typename... Out, typename Traits_>
+  class DataHandleMixin<std::tuple<Out...>, void, Traits_> : public BaseClass_t<Traits_> {
+    static_assert( std::is_base_of<Algorithm, BaseClass_t<Traits_>>::value, "BaseClass must inherit from Algorithm" );
+
+    template <typename OArgs, std::size_t... J>
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const OArgs& outputs,
+                     std::index_sequence<J...> )
+        : BaseClass_t<Traits_>( name, pSvcLocator )
+        , m_outputs( std::tuple_cat( std::forward_as_tuple( this ), std::get<J>( outputs ) )... ) {
+      // make sure this algorithm is seen as reentrant by Gaudi
+      this->setProperty( "Cardinality", 0 );
+    }
+
+  public:
+    constexpr static std::size_t N_out = sizeof...( Out );
+    using KeyValue                     = std::pair<std::string, std::string>;
+    using KeyValues                    = std::pair<std::string, std::vector<std::string>>;
+
+    // generic constructor:  0 -> N
+    DataHandleMixin( const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N_out>& outputs )
+        : DataHandleMixin( name, pSvcLocator, outputs, std::index_sequence_for<Out...>{} ) {}
+
+    // 0 -> 1
+    DataHandleMixin( const std::string& name, ISvcLocator* locator, const KeyValue& output )
+        : DataHandleMixin( name, locator, std::array<KeyValue, 1>{output} ) {}
+
+    template <std::size_t N = 0>
+    const std::string& outputLocation() const {
+      return std::get<N>( m_outputs ).objKey();
+    }
+    constexpr unsigned int outputLocationSize() const { return N_out; }
+
+  protected:
+    bool isReEntrant() const override { return true; }
+
+    std::tuple<details::OutputHandle_t<Traits_, Out>...> m_outputs;
+  };
+
+  /////////////////
+  template <typename Fun, typename Container, typename... Args>
+  constexpr void applyPostProcessing( const Fun&, Container&, Args... ) {
+    static_assert( sizeof...( Args ) == 0, "Args should not be used!" );
+  }
+
+  template <typename Fun, typename Container>
+  auto applyPostProcessing( const Fun& fun, Container& c ) -> decltype( fun.postprocess( c ), void() ) {
+    fun.postprocess( c );
+  }
+
+} // namespace Gaudi::Functional::details
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/FunctionalUtilities.h b/GaudiAlg/GaudiAlg/FunctionalUtilities.h
index 33a9ff5c12d5df7a249da20e08d277950ae0b748..f02dba5d755f5b5e3ed7d499a4ac68e87519414b 100644
--- a/GaudiAlg/GaudiAlg/FunctionalUtilities.h
+++ b/GaudiAlg/GaudiAlg/FunctionalUtilities.h
@@ -11,82 +11,77 @@
 #include "GaudiKernel/DataObjectHandle.h"
 #include "GaudiKernel/SerializeSTL.h"
 
-namespace Gaudi {
-  namespace Functional {
-
-    // This utility is needed when the inputs of a functional algorithm may be stored in several locations
-    inline std::string concat_alternatives( std::initializer_list<std::string> c ) {
-      return boost::algorithm::join( c, ":" );
-    }
-
-    template <typename... Strings>
-    std::string concat_alternatives( const Strings&... s ) {
-      return concat_alternatives( std::initializer_list<std::string>{s...} );
-    }
-
-    inline void updateHandleLocation( IProperty& parent, const std::string& prop, const std::string& newLoc ) {
-      auto sc = parent.setProperty( prop, newLoc );
-      if ( sc.isFailure() ) throw GaudiException( "Could not set Property", prop + " -> " + newLoc, sc );
-    }
-
-    inline void updateHandleLocations( IProperty& parent, const std::string& prop,
-                                       const std::vector<std::string>& newLocs ) {
-      std::ostringstream ss;
-      GaudiUtils::details::ostream_joiner( ss << '[', newLocs, ", ", []( std::ostream & os, const auto& i ) -> auto& {
-        return os << "'" << i << "'";
-      } ) << ']';
-      auto sc = parent.setProperty( prop, ss.str() );
-      if ( sc.isFailure() ) throw GaudiException( "Could not set Property", prop + " -> " + ss.str(), sc );
-    }
-
-    [[deprecated( "please use updateHandleLocation instead" )]] inline void
-    updateReadHandleLocation( IProperty& parent, const std::string& prop, const std::string& newLoc ) {
-      return updateHandleLocation( parent, prop, newLoc );
-    }
-
-    namespace Traits {
-
-      // traits classes used to customize Transformer and FilterPredicate
-      // Define the types to to be used as baseclass, and as in- resp. output hanldes.
-      // In case a type is not specified in the traits struct, a default is used.
-      //
-      // The defaults are:
-      //
-      //      using BaseClass = GaudiAlgorithm
-      //      template <typename T> using InputHandle = DataObjectHandle<T>;
-      //      template <typename T> using OutputHandle = DataObjectHandle<T>;
-      //
-
-      // the best way to 'compose' traits is by inheriting them one-by-one...
-      template <typename... Base>
-      struct use_ : Base... {};
-
-      // helper classes one can inherit from to specify a specific trait
-      template <typename Base>
-      struct BaseClass_t {
-        using BaseClass = Base;
-      };
-
-      template <template <typename> class Handle>
-      struct InputHandle_t {
-        template <typename T>
-        using InputHandle = Handle<T>;
-      };
-
-      template <template <typename> class Handle>
-      struct OutputHandle_t {
-        template <typename T>
-        using OutputHandle = Handle<T>;
-      };
-
-      // this uses the defaults -- and it itself is the default ;-)
-      using useDefaults = use_<>;
-
-      // this example uses GaudiHistoAlg as baseclass, and the default handle types for
-      // input and output
-      using useGaudiHistoAlg = use_<BaseClass_t<GaudiHistoAlg>>;
-    } // namespace Traits
-  }   // namespace Functional
-} // namespace Gaudi
+namespace Gaudi::Functional {
+
+  // This utility is needed when the inputs of a functional algorithm may be stored in several locations
+  inline std::string concat_alternatives( std::initializer_list<std::string> c ) {
+    return boost::algorithm::join( c, ":" );
+  }
+
+  template <typename... Strings>
+  std::string concat_alternatives( const Strings&... s ) {
+    return concat_alternatives( std::initializer_list<std::string>{s...} );
+  }
+
+  [[deprecated( "please use `updateHandleLocation` instead of `Gaudi::Functional::updateHandleLocation`" )]] inline void
+  updateHandleLocation( IProperty& parent, const std::string& prop, const std::string& newLoc ) {
+    auto sc = parent.setProperty( prop, newLoc );
+    if ( sc.isFailure() ) throw GaudiException( "Could not set Property", prop + " -> " + newLoc, sc );
+  }
+
+  [[deprecated(
+      "please use `updateHandleLocations` instead of `Gaudi::Functional::updateHandleLocations`" )]] inline void
+  updateHandleLocations( IProperty& parent, const std::string& prop, const std::vector<std::string>& newLocs ) {
+    std::ostringstream ss;
+    GaudiUtils::details::ostream_joiner( ss << '[', newLocs, ", ", []( std::ostream & os, const auto& i ) -> auto& {
+      return os << "'" << i << "'";
+    } ) << ']';
+    auto sc = parent.setProperty( prop, ss.str() );
+    if ( sc.isFailure() ) throw GaudiException( "Could not set Property", prop + " -> " + ss.str(), sc );
+  }
+
+  namespace Traits {
+
+    // traits classes used to customize Transformer and FilterPredicate
+    // Define the types to to be used as baseclass, and as in- resp. output hanldes.
+    // In case a type is not specified in the traits struct, a default is used.
+    //
+    // The defaults are:
+    //
+    //      using BaseClass = GaudiAlgorithm
+    //      template <typename T> using InputHandle = DataObjectHandle<T>;
+    //      template <typename T> using OutputHandle = DataObjectHandle<T>;
+    //
+
+    // the best way to 'compose' traits is by inheriting them one-by-one...
+    template <typename... Base>
+    struct use_ : Base... {};
+
+    // helper classes one can inherit from to specify a specific trait
+    template <typename Base>
+    struct BaseClass_t {
+      using BaseClass = Base;
+    };
+
+    template <template <typename> class Handle>
+    struct InputHandle_t {
+      template <typename T>
+      using InputHandle = Handle<T>;
+    };
+
+    template <template <typename> class Handle>
+    struct OutputHandle_t {
+      template <typename T>
+      using OutputHandle = Handle<T>;
+    };
+
+    // this uses the defaults -- and it itself is the default ;-)
+    using useDefaults = use_<>;
+
+    // this example uses GaudiHistoAlg as baseclass, and the default handle types for
+    // input and output
+    using useGaudiHistoAlg = use_<BaseClass_t<GaudiHistoAlg>>;
+  } // namespace Traits
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/MergingTransformer.h b/GaudiAlg/GaudiAlg/MergingTransformer.h
index 49b5baea905c4ffce82b592c60c349698792cb04..af1bc9f891c36b2dd12fcf10c6fcfbd1b184048b 100644
--- a/GaudiAlg/GaudiAlg/MergingTransformer.h
+++ b/GaudiAlg/GaudiAlg/MergingTransformer.h
@@ -8,26 +8,40 @@
 #include "GaudiAlg/FunctionalDetails.h"
 #include "GaudiAlg/FunctionalUtilities.h"
 
-namespace Gaudi {
-  namespace Functional {
+namespace Gaudi::Functional {
 
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
-    class MergingTransformer;
+  using details::vector_of_const_;
+
+  namespace details {
 
-    using details::vector_of_const_;
+    template <typename Signature, typename Traits_, bool isLegacy>
+    class MergingTransformer;
 
     ////// Many of the same -> 1
     template <typename Out, typename In, typename Traits_>
-    class MergingTransformer<Out( const vector_of_const_<In>& ), Traits_>
+    class MergingTransformer<Out( const vector_of_const_<In>& ), Traits_, true>
         : public details::DataHandleMixin<std::tuple<Out>, void, Traits_> {
       using base_class = details::DataHandleMixin<std::tuple<Out>, void, Traits_>;
 
     public:
-      using KeyValue  = std::pair<std::string, std::string>;
-      using KeyValues = std::pair<std::string, std::vector<std::string>>;
+      using KeyValue  = typename base_class::KeyValue;
+      using KeyValues = typename base_class::KeyValues;
 
       MergingTransformer( const std::string& name, ISvcLocator* locator, const KeyValues& inputs,
-                          const KeyValue& output );
+                          const KeyValue& output )
+          : base_class( name, locator, output )
+          , m_inputLocations{this, inputs.first, inputs.second,
+                             [=]( Gaudi::Details::PropertyBase& ) {
+                               this->m_inputs = details::make_vector_of_handles<decltype( this->m_inputs )>(
+                                   this, m_inputLocations );
+                               if ( std::is_pointer<In>::value ) { // handle constructor does not (yet) allow to set
+                                                                   // optional flag... so do it
+                                                                   // explicitly here...
+                                 std::for_each( this->m_inputs.begin(), this->m_inputs.end(),
+                                                []( auto& h ) { h.setOptional( true ); } );
+                               }
+                             },
+                             Gaudi::Details::Property::ImmediatelyInvokeHandler{true}} {}
 
       // accessor to input Locations
       const std::string& inputLocation( unsigned int n ) const { return m_inputLocations.value()[n]; }
@@ -40,13 +54,12 @@ namespace Gaudi {
         std::transform( m_inputs.begin(), m_inputs.end(), std::back_inserter( ins ),
                         details::details2::get_from_handle<In>{} );
         try {
-          using details::as_const;
-          details::put( std::get<0>( this->m_outputs ), as_const( *this )( as_const( ins ) ) );
+          details::put( std::get<0>( this->m_outputs ), std::as_const( *this )( std::as_const( ins ) ) );
+          return StatusCode::SUCCESS;
         } catch ( GaudiException& e ) {
           ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
           return e.code();
         }
-        return StatusCode::SUCCESS;
       }
 
       virtual Out operator()( const vector_of_const_<In>& inputs ) const = 0;
@@ -62,24 +75,66 @@ namespace Gaudi {
     };
 
     template <typename Out, typename In, typename Traits_>
-    MergingTransformer<Out( const vector_of_const_<In>& ), Traits_>::MergingTransformer( const std::string& name,
-                                                                                         ISvcLocator*       pSvcLocator,
-                                                                                         const KeyValues&   inputs,
-                                                                                         const KeyValue&    output )
-        : base_class( name, pSvcLocator, output )
-        , m_inputLocations{this, inputs.first, inputs.second,
-                           [=]( Gaudi::Details::PropertyBase& ) {
-                             this->m_inputs =
-                                 details::make_vector_of_handles<decltype( this->m_inputs )>( this, m_inputLocations );
-                             if ( std::is_pointer<In>::value ) { // handle constructor does not (yet) allow to set
-                                                                 // optional flag... so do it
-                                                                 // explicitly here...
-                               std::for_each( this->m_inputs.begin(), this->m_inputs.end(),
-                                              []( auto& h ) { h.setOptional( true ); } );
-                             }
-                           },
-                           Gaudi::Details::Property::ImmediatelyInvokeHandler{true}} {}
-  } // namespace Functional
-} // namespace Gaudi
+    class MergingTransformer<Out( const vector_of_const_<In>& ), Traits_, false>
+        : public details::DataHandleMixin<std::tuple<Out>, void, Traits_> {
+      using base_class = details::DataHandleMixin<std::tuple<Out>, void, Traits_>;
+
+    public:
+      using KeyValue  = typename base_class::KeyValue;
+      using KeyValues = typename base_class::KeyValues;
+
+      MergingTransformer( const std::string& name, ISvcLocator* locator, const KeyValues& inputs,
+                          const KeyValue& output )
+          : base_class( name, locator, output )
+          , m_inputLocations{this, inputs.first, inputs.second,
+                             [=]( Gaudi::Details::PropertyBase& ) {
+                               this->m_inputs = details::make_vector_of_handles<decltype( this->m_inputs )>(
+                                   this, m_inputLocations );
+                               if ( std::is_pointer<In>::value ) { // handle constructor does not (yet) allow to set
+                                                                   // optional flag... so do it
+                                                                   // explicitly here...
+                                 std::for_each( this->m_inputs.begin(), this->m_inputs.end(),
+                                                []( auto& h ) { h.setOptional( true ); } );
+                               }
+                             },
+                             Gaudi::Details::Property::ImmediatelyInvokeHandler{true}} {}
+
+      // accessor to input Locations
+      const std::string& inputLocation( unsigned int n ) const { return m_inputLocations.value()[n]; }
+      unsigned int       inputLocationSize() const { return m_inputLocations.value().size(); }
+
+      // derived classes can NOT implement execute
+      StatusCode execute( const EventContext& ) const override final {
+        vector_of_const_<In> ins;
+        ins.reserve( m_inputs.size() );
+        std::transform( m_inputs.begin(), m_inputs.end(), std::back_inserter( ins ),
+                        details::details2::get_from_handle<In>{} );
+        try {
+          details::put( std::get<0>( this->m_outputs ), std::as_const( *this )( std::as_const( ins ) ) );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      virtual Out operator()( const vector_of_const_<In>& inputs ) const = 0;
+
+    private:
+      // if In is a pointer, it signals optional (as opposed to mandatory) input
+      template <typename T>
+      using InputHandle_t = details::InputHandle_t<Traits_, typename std::remove_pointer<T>::type>;
+      std::vector<InputHandle_t<In>>            m_inputs;         //   and make the handles properties instead...
+      Gaudi::Property<std::vector<std::string>> m_inputLocations; // TODO/FIXME: remove this duplication...
+      // TODO/FIXME: replace vector of string property + call-back with a
+      //             vector<handle> property ... as soon as declareProperty can deal with that.
+    };
+
+  } // namespace details
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using MergingTransformer = details::MergingTransformer<Signature, Traits_, details::isLegacy<Traits_>>;
+
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/Producer.h b/GaudiAlg/GaudiAlg/Producer.h
index f60b41a22e27c97f9bad1ffeec34e39675c00c22..734ad77818a173c2d705ba3e3ce5c759239bb7bc 100644
--- a/GaudiAlg/GaudiAlg/Producer.h
+++ b/GaudiAlg/GaudiAlg/Producer.h
@@ -5,14 +5,16 @@
 #include "GaudiAlg/FunctionalUtilities.h"
 #include <utility>
 
-namespace Gaudi {
-  namespace Functional {
+namespace Gaudi::Functional {
 
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
+  namespace details {
+
+    template <typename Signature, typename Traits_, bool isLegacy>
     class Producer;
 
     template <typename... Out, typename Traits_>
-    class Producer<std::tuple<Out...>(), Traits_> : public details::DataHandleMixin<std::tuple<Out...>, void, Traits_> {
+    class Producer<std::tuple<Out...>(), Traits_, true>
+        : public details::DataHandleMixin<std::tuple<Out...>, void, Traits_> {
     public:
       using details::DataHandleMixin<std::tuple<Out...>, void, Traits_>::DataHandleMixin;
 
@@ -25,14 +27,43 @@ namespace Gaudi {
                     [&ohandle...]( auto&&... data ) {
                       ( details::put( ohandle, std::forward<decltype( data )>( data ) ), ... );
                     },
-                    details::as_const( *this )() );
+                    std::as_const( *this )() );
+              },
+              this->m_outputs );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // ... instead, they must implement the following operator
+      virtual std::tuple<Out...> operator()() const = 0;
+    };
+
+    template <typename... Out, typename Traits_>
+    class Producer<std::tuple<Out...>(), Traits_, false>
+        : public details::DataHandleMixin<std::tuple<Out...>, void, Traits_> {
+    public:
+      using details::DataHandleMixin<std::tuple<Out...>, void, Traits_>::DataHandleMixin;
+
+      // derived classes are NOT allowed to implement execute ...
+      StatusCode execute( const EventContext& ) const override final {
+        try {
+          std::apply(
+              [&]( auto&... ohandle ) {
+                std::apply(
+                    [&ohandle...]( auto&&... data ) {
+                      ( details::put( ohandle, std::forward<decltype( data )>( data ) ), ... );
+                    },
+                    std::as_const( *this )() );
               },
               this->m_outputs );
+          return StatusCode::SUCCESS;
         } catch ( GaudiException& e ) {
           ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
           return e.code();
         }
-        return StatusCode::SUCCESS;
       }
 
       // ... instead, they must implement the following operator
@@ -40,24 +71,47 @@ namespace Gaudi {
     };
 
     template <typename Out, typename Traits_>
-    class Producer<Out(), Traits_> : public details::DataHandleMixin<std::tuple<Out>, void, Traits_> {
+    class Producer<Out(), Traits_, true> : public details::DataHandleMixin<std::tuple<Out>, void, Traits_> {
     public:
       using details::DataHandleMixin<std::tuple<Out>, void, Traits_>::DataHandleMixin;
       // derived classes are NOT allowed to implement execute ...
       StatusCode execute() override final {
         try {
-          details::put( std::get<0>( this->m_outputs ), details::as_const( *this )() );
+          details::put( std::get<0>( this->m_outputs ), std::as_const( *this )() );
+          return StatusCode::SUCCESS;
         } catch ( GaudiException& e ) {
           ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
           return e.code();
         }
-        return StatusCode::SUCCESS;
       }
 
       // ... instead, they must implement the following operator
       virtual Out operator()() const = 0;
     };
-  } // namespace Functional
-} // namespace Gaudi
+
+    template <typename Out, typename Traits_>
+    class Producer<Out(), Traits_, false> : public details::DataHandleMixin<std::tuple<Out>, void, Traits_> {
+    public:
+      using details::DataHandleMixin<std::tuple<Out>, void, Traits_>::DataHandleMixin;
+      // derived classes are NOT allowed to implement execute ...
+      StatusCode execute( const EventContext& ) const override final {
+        try {
+          details::put( std::get<0>( this->m_outputs ), std::as_const( *this )() );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // ... instead, they must implement the following operator
+      virtual Out operator()() const = 0;
+    };
+  } // namespace details
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using Producer = details::Producer<Signature, Traits_, details::isLegacy<Traits_>>;
+
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/SplittingTransformer.h b/GaudiAlg/GaudiAlg/SplittingTransformer.h
index 1c0d363651d974a692a6dafdb596b93479b17a9d..fdb0a434c723af8af15ed3f96c768e8542df6b88 100644
--- a/GaudiAlg/GaudiAlg/SplittingTransformer.h
+++ b/GaudiAlg/GaudiAlg/SplittingTransformer.h
@@ -8,30 +8,44 @@
 #include "GaudiAlg/FunctionalDetails.h"
 #include "GaudiAlg/FunctionalUtilities.h"
 
-namespace Gaudi {
-  namespace Functional {
+namespace Gaudi::Functional {
 
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
-    class SplittingTransformer;
+  template <typename Container>
+  using vector_of_ = std::vector<Container>;
+  template <typename Container>
+  using vector_of_optional_ = std::vector<boost::optional<Container>>;
+
+  namespace details {
 
-    template <typename Container>
-    using vector_of_ = std::vector<Container>;
-    template <typename Container>
-    using vector_of_optional_ = std::vector<boost::optional<Container>>;
+    template <typename Signature, typename Traits_, bool isLegacy>
+    class SplittingTransformer;
 
     ////// N -> Many of the same one (value of Many not known at compile time, but known at configuration time)
     template <typename Out, typename... In, typename Traits_>
-    class SplittingTransformer<vector_of_<Out>( const In&... ), Traits_>
+    class SplittingTransformer<vector_of_<Out>( const In&... ), Traits_, true>
         : public details::DataHandleMixin<void, std::tuple<In...>, Traits_> {
       using base_class = details::DataHandleMixin<void, std::tuple<In...>, Traits_>;
 
     public:
-      constexpr static std::size_t N = sizeof...( In );
-      using KeyValue                 = std::pair<std::string, std::string>;
-      using KeyValues                = std::pair<std::string, std::vector<std::string>>;
+      constexpr static std::size_t N = base_class::N_in;
+      using KeyValue                 = typename base_class::KeyValue;
+      using KeyValues                = typename base_class::KeyValues;
 
       SplittingTransformer( const std::string& name, ISvcLocator* locator, const std::array<KeyValue, N>& inputs,
-                            const KeyValues& output );
+                            const KeyValues& outputs )
+          : base_class( name, locator, inputs )
+          , m_outputLocations( this, outputs.first, outputs.second,
+                               [=]( Gaudi::Details::PropertyBase& ) {
+                                 this->m_outputs = details::make_vector_of_handles<decltype( this->m_outputs )>(
+                                     this, m_outputLocations );
+                                 if ( details::is_optional<Out>::value ) { // handle constructor does not (yet) allow to
+                                                                           // set optional flag... so
+                                                                           // do it explicitly here...
+                                   std::for_each( this->m_outputs.begin(), this->m_outputs.end(),
+                                                  []( auto& h ) { h.setOptional( true ); } );
+                                 }
+                               },
+                               Gaudi::Details::Property::ImmediatelyInvokeHandler{true} ) {}
 
       SplittingTransformer( const std::string& name, ISvcLocator* locator, const KeyValue& input,
                             const KeyValues& output )
@@ -47,7 +61,6 @@ namespace Gaudi {
       StatusCode execute() override final {
         try {
           // TODO:FIXME: how does operator() know the number and order of expected outputs?
-          using details::as_const;
           auto out = details::filter_evtcontext_t<In...>::apply( *this, this->m_inputs );
           if ( out.size() != m_outputs.size() ) {
             throw GaudiException( "Error during transform: expected " + std::to_string( m_outputs.size() ) +
@@ -74,25 +87,78 @@ namespace Gaudi {
       Gaudi::Property<std::vector<std::string>> m_outputLocations; // TODO/FIXME  for now: use a call-back to update the
                                                                    // actual handles!
     };
-
     template <typename Out, typename... In, typename Traits_>
-    SplittingTransformer<vector_of_<Out>( const In&... ), Traits_>::SplittingTransformer(
-        const std::string& name, ISvcLocator* pSvcLocator, const std::array<KeyValue, N>& inputs,
-        const KeyValues& outputs )
-        : base_class( name, pSvcLocator, inputs )
-        , m_outputLocations( this, outputs.first, outputs.second,
-                             [=]( Gaudi::Details::PropertyBase& ) {
-                               this->m_outputs = details::make_vector_of_handles<decltype( this->m_outputs )>(
-                                   this, m_outputLocations );
-                               if ( details::is_optional<Out>::value ) { // handle constructor does not (yet) allow to
-                                                                         // set optional flag... so
-                                                                         // do it explicitly here...
-                                 std::for_each( this->m_outputs.begin(), this->m_outputs.end(),
-                                                []( auto& h ) { h.setOptional( true ); } );
-                               }
-                             },
-                             Gaudi::Details::Property::ImmediatelyInvokeHandler{true} ) {}
-  } // namespace Functional
-} // namespace Gaudi
+    class SplittingTransformer<vector_of_<Out>( const In&... ), Traits_, false>
+        : public details::DataHandleMixin<void, std::tuple<In...>, Traits_> {
+      using base_class = details::DataHandleMixin<void, std::tuple<In...>, Traits_>;
+
+    public:
+      constexpr static std::size_t N = base_class::N_in;
+      using KeyValue                 = typename base_class::KeyValue;
+      using KeyValues                = typename base_class::KeyValues;
+
+      SplittingTransformer( const std::string& name, ISvcLocator* locator, const std::array<KeyValue, N>& inputs,
+                            const KeyValues& outputs )
+          : base_class( name, locator, inputs )
+          , m_outputLocations( this, outputs.first, outputs.second,
+                               [=]( Gaudi::Details::PropertyBase& ) {
+                                 this->m_outputs = details::make_vector_of_handles<decltype( this->m_outputs )>(
+                                     this, m_outputLocations );
+                                 if ( details::is_optional<Out>::value ) { // handle constructor does not (yet) allow to
+                                                                           // set optional flag... so
+                                                                           // do it explicitly here...
+                                   std::for_each( this->m_outputs.begin(), this->m_outputs.end(),
+                                                  []( auto& h ) { h.setOptional( true ); } );
+                                 }
+                               },
+                               Gaudi::Details::Property::ImmediatelyInvokeHandler{true} ) {}
+
+      SplittingTransformer( const std::string& name, ISvcLocator* locator, const KeyValue& input,
+                            const KeyValues& output )
+          : SplittingTransformer( name, locator, std::array<KeyValue, 1>{input}, output ) {
+        static_assert( N == 1, "single input argument requires single input signature" );
+      }
+
+      // accessor to output Locations
+      const std::string& outputLocation( unsigned int n ) const { return m_outputLocations.value()[n]; }
+      unsigned int       outputLocationSize() const { return m_outputLocations.value().size(); }
+
+      // derived classes can NOT implement execute
+      StatusCode execute( const EventContext& ctx ) const override final {
+        try {
+          // TODO:FIXME: how does operator() know the number and order of expected outputs?
+          auto out = details::filter_evtcontext_t<In...>::apply( *this, ctx, this->m_inputs );
+          if ( out.size() != m_outputs.size() ) {
+            throw GaudiException( "Error during transform: expected " + std::to_string( m_outputs.size() ) +
+                                      " containers, got " + std::to_string( out.size() ) + " instead",
+                                  this->name(), StatusCode::FAILURE );
+          }
+          for ( unsigned i = 0; i != out.size(); ++i ) details::put( m_outputs[i], std::move( out[i] ) );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // TODO/FIXME: how does the callee know in which order to produce the outputs?
+      //             (note: 'missing' items can be specified by making Out an boost::optional<Out>,
+      //              and only those entries which contain an Out are stored)
+      virtual vector_of_<Out> operator()( const In&... ) const = 0;
+
+    private:
+      template <typename T>
+      using OutputHandle = details::OutputHandle_t<Traits_, details::remove_optional_t<T>>;
+      std::vector<OutputHandle<Out>>            m_outputs;
+      Gaudi::Property<std::vector<std::string>> m_outputLocations; // TODO/FIXME  for now: use a call-back to update the
+                                                                   // actual handles!
+    };
+
+  } // namespace details
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using SplittingTransformer = details::SplittingTransformer<Signature, Traits_, details::isLegacy<Traits_>>;
+
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiAlg/GaudiAlg/Transformer.h b/GaudiAlg/GaudiAlg/Transformer.h
index 796b810f063147c931270ac8b2c91d91f3672358..e1585f155f74ed6f61fad59e739feda9fa86e449 100644
--- a/GaudiAlg/GaudiAlg/Transformer.h
+++ b/GaudiAlg/GaudiAlg/Transformer.h
@@ -13,15 +13,16 @@
 //   b) are encouraged not to have state which depends on the events
 //      (eg. histograms, counters will have to be mutable)
 
-namespace Gaudi {
-  namespace Functional {
+namespace Gaudi ::Functional {
 
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
+  namespace details {
+
+    template <typename Signature, typename Traits_, bool isLegacy>
     class Transformer;
 
     // general N -> 1 algorithms
     template <typename Out, typename... In, typename Traits_>
-    class Transformer<Out( const In&... ), Traits_>
+    class Transformer<Out( const In&... ), Traits_, true>
         : public details::DataHandleMixin<std::tuple<Out>, details::filter_evtcontext<In...>, Traits_> {
     public:
       using details::DataHandleMixin<std::tuple<Out>, details::filter_evtcontext<In...>, Traits_>::DataHandleMixin;
@@ -42,14 +43,36 @@ namespace Gaudi {
       virtual Out operator()( const In&... ) const = 0;
     };
 
+    template <typename Out, typename... In, typename Traits_>
+    class Transformer<Out( const In&... ), Traits_, false>
+        : public details::DataHandleMixin<std::tuple<Out>, details::filter_evtcontext<In...>, Traits_> {
+    public:
+      using details::DataHandleMixin<std::tuple<Out>, details::filter_evtcontext<In...>, Traits_>::DataHandleMixin;
+
+      // derived classes can NOT implement execute
+      StatusCode execute( const EventContext& ctx ) const override final {
+        try {
+          details::put( std::get<0>( this->m_outputs ),
+                        details::filter_evtcontext_t<In...>::apply( *this, ctx, this->m_inputs ) );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // instead they MUST implement this operator
+      virtual Out operator()( const In&... ) const = 0;
+    };
+
     //
     // general N -> M algorithms
     //
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
+    template <typename Signature, typename Traits_, bool isLegacy>
     class MultiTransformer;
 
     template <typename... Out, typename... In, typename Traits_>
-    class MultiTransformer<std::tuple<Out...>( const In&... ), Traits_>
+    class MultiTransformer<std::tuple<Out...>( const In&... ), Traits_, true>
         : public details::DataHandleMixin<std::tuple<Out...>, details::filter_evtcontext<In...>, Traits_> {
     public:
       using details::DataHandleMixin<std::tuple<Out...>, details::filter_evtcontext<In...>, Traits_>::DataHandleMixin;
@@ -71,6 +94,45 @@ namespace Gaudi {
                     },
                     details::filter_evtcontext_t<In...>::apply( *this, this->m_inputs ) );
 
+#if defined( __clang__ ) && ( __clang_major__ < 6 )
+#  pragma clang diagnostic pop
+#endif
+              },
+              this->m_outputs );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // instead they MUST implement this operator
+      virtual std::tuple<Out...> operator()( const In&... ) const = 0;
+    };
+
+    template <typename... Out, typename... In, typename Traits_>
+    class MultiTransformer<std::tuple<Out...>( const In&... ), Traits_, false>
+        : public details::DataHandleMixin<std::tuple<Out...>, details::filter_evtcontext<In...>, Traits_> {
+    public:
+      using details::DataHandleMixin<std::tuple<Out...>, details::filter_evtcontext<In...>, Traits_>::DataHandleMixin;
+
+      // derived classes can NOT implement execute
+      StatusCode execute( const EventContext& ctx ) const override final {
+        try {
+          std::apply(
+              [this, &ctx]( auto&... ohandle ) {
+
+#if defined( __clang__ ) && ( __clang_major__ < 6 )
+// clang-5 gives a spurious warning about not using the captured `ohandle`
+#  pragma clang diagnostic push
+#  pragma clang diagnostic ignored "-Wunused-lambda-capture"
+#endif
+                std::apply(
+                    [&ohandle...]( auto&&... data ) {
+                      ( details::put( ohandle, std::forward<decltype( data )>( data ) ), ... );
+                    },
+                    details::filter_evtcontext_t<In...>::apply( *this, ctx, this->m_inputs ) );
+
 #if defined( __clang__ ) && ( __clang_major__ < 6 )
 #  pragma clang diagnostic pop
 #endif
@@ -90,11 +152,11 @@ namespace Gaudi {
     //
     // general N -> M algorithms with filter functionality
     //
-    template <typename Signature, typename Traits_ = Traits::useDefaults>
+    template <typename Signature, typename Traits_, bool isLegacy>
     class MultiTransformerFilter;
 
     template <typename... Out, typename... In, typename Traits_>
-    class MultiTransformerFilter<std::tuple<Out...>( const In&... ), Traits_>
+    class MultiTransformerFilter<std::tuple<Out...>( const In&... ), Traits_, true>
         : public details::DataHandleMixin<std::tuple<Out...>, std::tuple<In...>, Traits_> {
     public:
       using details::DataHandleMixin<std::tuple<Out...>, std::tuple<In...>, Traits_>::DataHandleMixin;
@@ -122,7 +184,47 @@ namespace Gaudi {
       // instead they MUST implement this operator
       virtual std::tuple<bool, Out...> operator()( const In&... ) const = 0;
     };
-  } // namespace Functional
-} // namespace Gaudi
+
+    template <typename... Out, typename... In, typename Traits_>
+    class MultiTransformerFilter<std::tuple<Out...>( const In&... ), Traits_, false>
+        : public details::DataHandleMixin<std::tuple<Out...>, std::tuple<In...>, Traits_> {
+    public:
+      using details::DataHandleMixin<std::tuple<Out...>, std::tuple<In...>, Traits_>::DataHandleMixin;
+
+      // derived classes can NOT implement execute
+      StatusCode execute( const EventContext& ctx ) const override final {
+        try {
+          std::apply(
+              [&]( auto&... ohandle ) {
+                std::apply(
+                    [&ohandle..., this]( bool passed, auto&&... data ) {
+                      this->setFilterPassed( passed );
+                      ( details::put( ohandle, std::forward<decltype( data )>( data ) ), ... );
+                    },
+                    details::filter_evtcontext_t<In...>::apply( *this, ctx, this->m_inputs ) );
+              },
+              this->m_outputs );
+          return StatusCode::SUCCESS;
+        } catch ( GaudiException& e ) {
+          ( e.code() ? this->warning() : this->error() ) << e.message() << endmsg;
+          return e.code();
+        }
+      }
+
+      // instead they MUST implement this operator
+      virtual std::tuple<bool, Out...> operator()( const In&... ) const = 0;
+    };
+  } // namespace details
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using Transformer = details::Transformer<Signature, Traits_, details::isLegacy<Traits_>>;
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using MultiTransformer = details::MultiTransformer<Signature, Traits_, details::isLegacy<Traits_>>;
+
+  template <typename Signature, typename Traits_ = Traits::useDefaults>
+  using MultiTransformerFilter = details::MultiTransformerFilter<Signature, Traits_, details::isLegacy<Traits_>>;
+
+} // namespace Gaudi::Functional
 
 #endif
diff --git a/GaudiExamples/src/FunctionalAlgorithms/MakeAndConsume.cpp b/GaudiExamples/src/FunctionalAlgorithms/MakeAndConsume.cpp
index a75992f1190968bbfe9d6b263eff2244d37bad19..fef37ccff98b5e1e45dce3fd117272c2b5963d53 100644
--- a/GaudiExamples/src/FunctionalAlgorithms/MakeAndConsume.cpp
+++ b/GaudiExamples/src/FunctionalAlgorithms/MakeAndConsume.cpp
@@ -5,247 +5,246 @@
 #include "GaudiKernel/KeyedContainer.h"
 #include <cmath>
 
-namespace Gaudi {
-  namespace Examples {
+namespace Gaudi::Examples {
 
-    using BaseClass_t = Gaudi::Functional::Traits::BaseClass_t<::Algorithm>;
+  using LegacyBaseClass_t = Gaudi::Functional::Traits::BaseClass_t<::Algorithm>;
+  using BaseClass_t       = Gaudi::Functional::Traits::BaseClass_t<Gaudi::Algorithm>;
 
-    struct IntDataProducer final : Gaudi::Functional::Producer<int(), BaseClass_t> {
+  struct IntDataProducer final : Gaudi::Functional::Producer<int(), BaseClass_t> {
 
-      IntDataProducer( const std::string& name, ISvcLocator* svcLoc )
-          : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyInt" ) ) {}
+    IntDataProducer( const std::string& name, ISvcLocator* svcLoc )
+        : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyInt" ) ) {}
 
-      int operator()() const override {
-        info() << "executing IntDataProducer, storing 7 into " << outputLocation() << endmsg;
-        return 7;
-      }
-    };
+    int operator()() const override {
+      info() << "executing IntDataProducer, storing 7 into " << outputLocation() << endmsg;
+      return 7;
+    }
+  };
 
-    DECLARE_COMPONENT( IntDataProducer )
+  DECLARE_COMPONENT( IntDataProducer )
 
-    struct VectorDataProducer final : Gaudi::Functional::Producer<std::vector<int>(), BaseClass_t> {
+  struct VectorDataProducer final : Gaudi::Functional::Producer<std::vector<int>(), BaseClass_t> {
 
-      VectorDataProducer( const std::string& name, ISvcLocator* svcLoc )
-          : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyVector" ) ) {}
+    VectorDataProducer( const std::string& name, ISvcLocator* svcLoc )
+        : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyVector" ) ) {}
 
-      std::vector<int> operator()() const override {
-        info() << "executing VectorDataProducer, storing [3,3,3,3] into " << outputLocation() << endmsg;
-        return {3, 3, 3, 3};
-      }
-    };
+    std::vector<int> operator()() const override {
+      info() << "executing VectorDataProducer, storing [3,3,3,3] into " << outputLocation() << endmsg;
+      return {3, 3, 3, 3};
+    }
+  };
 
-    DECLARE_COMPONENT( VectorDataProducer )
+  DECLARE_COMPONENT( VectorDataProducer )
 
-    using int_container = KeyedContainer<KeyedObject<int>, Containers::HashMap>;
-    struct KeyedDataProducer final : Gaudi::Functional::Producer<int_container(), BaseClass_t> {
+  using int_container = KeyedContainer<KeyedObject<int>, Containers::HashMap>;
+  struct KeyedDataProducer final : Gaudi::Functional::Producer<int_container(), BaseClass_t> {
 
-      KeyedDataProducer( const std::string& name, ISvcLocator* svcLoc )
-          : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyKeyed" ) ) {}
+    KeyedDataProducer( const std::string& name, ISvcLocator* svcLoc )
+        : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyKeyed" ) ) {}
 
-      int_container operator()() const override {
-        int_container container;
-        auto          a = new KeyedObject<int>{4};
-        container.add( a );
-        auto b = new KeyedObject<int>{5};
-        container.add( b );
-        info() << "executing KeyedDataProducer, storing [4,5] into " << outputLocation() << endmsg;
-        return container;
-      }
-    };
+    int_container operator()() const override {
+      int_container container;
+      auto          a = new KeyedObject<int>{4};
+      container.add( a );
+      auto b = new KeyedObject<int>{5};
+      container.add( b );
+      info() << "executing KeyedDataProducer, storing [4,5] into " << outputLocation() << endmsg;
+      return container;
+    }
+  };
 
-    DECLARE_COMPONENT( KeyedDataProducer )
+  DECLARE_COMPONENT( KeyedDataProducer )
 
-    struct IntDataConsumer final : Gaudi::Functional::Consumer<void( const int& ), BaseClass_t> {
+  struct IntDataConsumer final : Gaudi::Functional::Consumer<void( const int& ), BaseClass_t> {
 
-      IntDataConsumer( const std::string& name, ISvcLocator* svcLoc )
-          : Consumer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyInt" ) ) {}
+    IntDataConsumer( const std::string& name, ISvcLocator* svcLoc )
+        : Consumer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyInt" ) ) {}
 
-      void operator()( const int& input ) const override {
-        info() << "executing IntDataConsumer, consuming " << input << " from " << inputLocation() << endmsg;
-      }
-    };
+    void operator()( const int& input ) const override {
+      info() << "executing IntDataConsumer, consuming " << input << " from " << inputLocation() << endmsg;
+    }
+  };
 
-    DECLARE_COMPONENT( IntDataConsumer )
+  DECLARE_COMPONENT( IntDataConsumer )
 
-    struct IntToFloatData final : Gaudi::Functional::Transformer<float( const int& ), BaseClass_t> {
+  struct IntToFloatData final : Gaudi::Functional::Transformer<float( const int& ), LegacyBaseClass_t> {
 
-      IntToFloatData( const std::string& name, ISvcLocator* svcLoc )
-          : Transformer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyInt" ),
-                         KeyValue( "OutputLocation", "/Event/MyFloat" ) ) {}
+    IntToFloatData( const std::string& name, ISvcLocator* svcLoc )
+        : Transformer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyInt" ),
+                       KeyValue( "OutputLocation", "/Event/MyFloat" ) ) {}
 
-      float operator()( const int& input ) const override {
-        info() << "Converting: " << input << " from " << inputLocation() << " and storing it into " << outputLocation()
-               << endmsg;
-        return input;
-      }
-    };
-
-    DECLARE_COMPONENT( IntToFloatData )
-
-    struct IntIntToFloatFloatData final
-        : Gaudi::Functional::MultiTransformer<std::tuple<float, float>( const int&, const int& ), BaseClass_t> {
-      IntIntToFloatFloatData( const std::string& name, ISvcLocator* svcLoc )
-          : MultiTransformer(
-                name, svcLoc,
-                {KeyValue( "InputLocation1", {"/Event/MyInt"} ), KeyValue( "InputLocation2", {"/Event/MyOtherInt"} )},
-                {KeyValue( "OutputLocation1", {"/Event/MyMultiFloat1"} ),
-                 KeyValue( "OutputLocation2", {"/Event/MyMultiFloat2"} )} ) {}
-
-      std::tuple<float, float> operator()( const int& input1, const int& input2 ) const override {
-        info() << "Number of inputs : " << inputLocationSize() << ", number of outputs : " << outputLocationSize()
-               << endmsg;
-        info() << "Converting " << input1 << " from " << inputLocation<0>() << " and " << input2 << " from "
-               << inputLocation<1>() << endmsg;
-        info() << "Storing results into " << outputLocation<0>() << " and " << outputLocation<1>() << endmsg;
-        return std::tuple<float, float>{input1, input2};
-      }
-    };
+    float operator()( const int& input ) const override {
+      info() << "Converting: " << input << " from " << inputLocation() << " and storing it into " << outputLocation()
+             << endmsg;
+      return input;
+    }
+  };
 
-    DECLARE_COMPONENT( IntIntToFloatFloatData )
+  DECLARE_COMPONENT( IntToFloatData )
 
-    struct FloatDataConsumer final : Gaudi::Functional::Consumer<void( const float& ), BaseClass_t> {
+  struct IntIntToFloatFloatData final
+      : Gaudi::Functional::MultiTransformer<std::tuple<float, float>( const int&, const int& ), LegacyBaseClass_t> {
+    IntIntToFloatFloatData( const std::string& name, ISvcLocator* svcLoc )
+        : MultiTransformer(
+              name, svcLoc,
+              {KeyValue( "InputLocation1", {"/Event/MyInt"} ), KeyValue( "InputLocation2", {"/Event/MyOtherInt"} )},
+              {KeyValue( "OutputLocation1", {"/Event/MyMultiFloat1"} ),
+               KeyValue( "OutputLocation2", {"/Event/MyMultiFloat2"} )} ) {}
 
-      FloatDataConsumer( const std::string& name, ISvcLocator* svcLoc )
-          : Consumer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyFloat" ) ) {}
+    std::tuple<float, float> operator()( const int& input1, const int& input2 ) const override {
+      info() << "Number of inputs : " << inputLocationSize() << ", number of outputs : " << outputLocationSize()
+             << endmsg;
+      info() << "Converting " << input1 << " from " << inputLocation<0>() << " and " << input2 << " from "
+             << inputLocation<1>() << endmsg;
+      info() << "Storing results into " << outputLocation<0>() << " and " << outputLocation<1>() << endmsg;
+      return std::tuple<float, float>{input1, input2};
+    }
+  };
 
-      void operator()( const float& input ) const override {
-        info() << "executing FloatDataConsumer: " << input << endmsg;
-      }
-    };
+  DECLARE_COMPONENT( IntIntToFloatFloatData )
 
-    DECLARE_COMPONENT( FloatDataConsumer )
+  struct FloatDataConsumer final : Gaudi::Functional::Consumer<void( const float& ), BaseClass_t> {
 
-    struct ContextConsumer final : Gaudi::Functional::Consumer<void( const EventContext& )> {
+    FloatDataConsumer( const std::string& name, ISvcLocator* svcLoc )
+        : Consumer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyFloat" ) ) {}
 
-      using Gaudi::Functional::Consumer<void( const EventContext& )>::Consumer;
+    void operator()( const float& input ) const override {
+      info() << "executing FloatDataConsumer: " << input << endmsg;
+    }
+  };
 
-      void operator()( const EventContext& ctx ) const override {
-        info() << "executing ContextConsumer, got " << ctx << endmsg;
-      }
-    };
+  DECLARE_COMPONENT( FloatDataConsumer )
 
-    DECLARE_COMPONENT( ContextConsumer )
+  struct ContextConsumer final : Gaudi::Functional::Consumer<void( const EventContext& ), BaseClass_t> {
 
-    struct ContextIntConsumer final : Gaudi::Functional::Consumer<void( const EventContext&, const int& )> {
+    using Gaudi::Functional::Consumer<void( const EventContext& ), BaseClass_t>::Consumer;
 
-      ContextIntConsumer( const std::string& name, ISvcLocator* svcLoc )
-          : Consumer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyInt" ) ) {}
+    void operator()( const EventContext& ctx ) const override {
+      info() << "executing ContextConsumer, got " << ctx << endmsg;
+    }
+  };
 
-      void operator()( const EventContext& ctx, const int& i ) const override {
-        info() << "executing ContextIntConsumer, got context = " << ctx << ", int = " << i << endmsg;
-      }
-    };
+  DECLARE_COMPONENT( ContextConsumer )
+
+  struct ContextIntConsumer final : Gaudi::Functional::Consumer<void( const EventContext&, const int& ), BaseClass_t> {
 
-    DECLARE_COMPONENT( ContextIntConsumer )
+    ContextIntConsumer( const std::string& name, ISvcLocator* svcLoc )
+        : Consumer( name, svcLoc, KeyValue( "InputLocation", "/Event/MyInt" ) ) {}
+
+    void operator()( const EventContext& ctx, const int& i ) const override {
+      info() << "executing ContextIntConsumer, got context = " << ctx << ", int = " << i << endmsg;
+    }
+  };
 
-    struct VectorDoubleProducer final : Gaudi::Functional::Producer<std::vector<double>(), BaseClass_t> {
+  DECLARE_COMPONENT( ContextIntConsumer )
+
+  struct VectorDoubleProducer final : Gaudi::Functional::Producer<std::vector<double>(), BaseClass_t> {
+
+    VectorDoubleProducer( const std::string& name, ISvcLocator* svcLoc )
+        : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyVectorOfDoubles" ) ) {}
+
+    std::vector<double> operator()() const override {
+      info() << "storing vector<double> into " << outputLocation() << endmsg;
+      return {12.34, 56.78, 90.12, 34.56, 78.90};
+    }
+  };
 
-      VectorDoubleProducer( const std::string& name, ISvcLocator* svcLoc )
-          : Producer( name, svcLoc, KeyValue( "OutputLocation", "/Event/MyVectorOfDoubles" ) ) {}
+  DECLARE_COMPONENT( VectorDoubleProducer )
 
-      std::vector<double> operator()() const override {
-        info() << "storing vector<double> into " << outputLocation() << endmsg;
-        return {12.34, 56.78, 90.12, 34.56, 78.90};
-      }
-    };
-
-    DECLARE_COMPONENT( VectorDoubleProducer )
-
-    struct FrExpTransformer final
-        : Gaudi::Functional::MultiScalarTransformer<
-              FrExpTransformer, std::tuple<std::vector<double>, std::vector<int>>( const std::vector<double>& ),
-              BaseClass_t> {
-      FrExpTransformer( const std::string& name, ISvcLocator* svcLoc )
-          : MultiScalarTransformer( name, svcLoc, KeyValue{"InputDoubles", {"/Event/MyVectorOfDoubles"}},
-                                    {KeyValue{"OutputFractions", {"/Event/MyVectorOfFractions"}},
-                                     KeyValue{"OutputIntegers", {"/Event/MyVectorOfIntegers"}}} ) {}
-
-      using MultiScalarTransformer::operator();
-
-      std::tuple<double, int> operator()( const double& d ) const {
-        int    i;
-        double frac = std::frexp( d, &i );
-        info() << "Converting " << d << " -> " << frac << ", " << i << endmsg;
-        return {frac, i};
-      }
-    };
-    DECLARE_COMPONENT( FrExpTransformer )
-
-    struct OptFrExpTransformer final
-        : Gaudi::Functional::MultiScalarTransformer<
-              OptFrExpTransformer, std::tuple<std::vector<double>, std::vector<int>>( const std::vector<double>& ),
-              BaseClass_t> {
-      OptFrExpTransformer( const std::string& name, ISvcLocator* svcLoc )
-          : MultiScalarTransformer( name, svcLoc, KeyValue{"InputDoubles", {"/Event/MyVectorOfDoubles"}},
-                                    {KeyValue{"OutputFractions", {"/Event/OptMyVectorOfFractions"}},
-                                     KeyValue{"OutputIntegers", {"/Event/OptMyVectorOfIntegers"}}} ) {}
-
-      using MultiScalarTransformer::operator();
-
-      boost::optional<std::tuple<double, int>> operator()( const double& d ) const {
-        if ( d < 30. ) {
-          info() << "Skipping " << d << endmsg;
-          return {};
-        }
-        int    i;
-        double frac = std::frexp( d, &i );
-        info() << "Converting " << d << " -> " << frac << ", " << i << endmsg;
-        return std::make_tuple( frac, i );
-      }
-    };
-    DECLARE_COMPONENT( OptFrExpTransformer )
-
-    struct LdExpTransformer final
-        : Gaudi::Functional::ScalarTransformer<
-              LdExpTransformer, std::vector<double>( const std::vector<double>&, const std::vector<int>& ),
-              BaseClass_t> {
-      LdExpTransformer( const std::string& name, ISvcLocator* svcLoc )
-          : ScalarTransformer( name, svcLoc,
-                               {KeyValue{"InputFractions", {"/Event/MyVectorOfFractions"}},
-                                KeyValue{"InputIntegers", {"/Event/MyVectorOfIntegers"}}},
-                               {KeyValue{"OutputDoubles", {"/Event/MyNewVectorOfDoubles"}}} ) {}
-
-      using ScalarTransformer::operator();
-
-      double operator()( double frac, int i ) const {
-        double d = std::ldexp( frac, i );
-        info() << "Converting " << i << ", " << frac << " -> " << d << endmsg;
-        return d;
+  struct FrExpTransformer final
+      : Gaudi::Functional::MultiScalarTransformer<
+            FrExpTransformer, std::tuple<std::vector<double>, std::vector<int>>( const std::vector<double>& ),
+            LegacyBaseClass_t> {
+    FrExpTransformer( const std::string& name, ISvcLocator* svcLoc )
+        : MultiScalarTransformer( name, svcLoc, KeyValue{"InputDoubles", {"/Event/MyVectorOfDoubles"}},
+                                  {KeyValue{"OutputFractions", {"/Event/MyVectorOfFractions"}},
+                                   KeyValue{"OutputIntegers", {"/Event/MyVectorOfIntegers"}}} ) {}
+
+    using MultiScalarTransformer::operator();
+
+    std::tuple<double, int> operator()( const double& d ) const {
+      int    i;
+      double frac = std::frexp( d, &i );
+      info() << "Converting " << d << " -> " << frac << ", " << i << endmsg;
+      return {frac, i};
+    }
+  };
+  DECLARE_COMPONENT( FrExpTransformer )
+
+  struct OptFrExpTransformer final
+      : Gaudi::Functional::MultiScalarTransformer<
+            OptFrExpTransformer, std::tuple<std::vector<double>, std::vector<int>>( const std::vector<double>& ),
+            LegacyBaseClass_t> {
+    OptFrExpTransformer( const std::string& name, ISvcLocator* svcLoc )
+        : MultiScalarTransformer( name, svcLoc, KeyValue{"InputDoubles", {"/Event/MyVectorOfDoubles"}},
+                                  {KeyValue{"OutputFractions", {"/Event/OptMyVectorOfFractions"}},
+                                   KeyValue{"OutputIntegers", {"/Event/OptMyVectorOfIntegers"}}} ) {}
+
+    using MultiScalarTransformer::operator();
+
+    boost::optional<std::tuple<double, int>> operator()( const double& d ) const {
+      if ( d < 30. ) {
+        info() << "Skipping " << d << endmsg;
+        return {};
       }
-    };
-    DECLARE_COMPONENT( LdExpTransformer )
-
-    struct OptLdExpTransformer final
-        : Gaudi::Functional::ScalarTransformer<
-              OptLdExpTransformer, std::vector<double>( const std::vector<double>&, const std::vector<int>& ),
-              BaseClass_t> {
-      OptLdExpTransformer( const std::string& name, ISvcLocator* svcLoc )
-          : ScalarTransformer( name, svcLoc,
-                               {KeyValue{"InputFractions", {"/Event/MyVectorOfFractions"}},
-                                KeyValue{"InputIntegers", {"/Event/MyVectorOfIntegers"}}},
-                               {KeyValue{"OutputDoubles", {"/Event/MyOptVectorOfDoubles"}}} ) {}
-
-      using ScalarTransformer::operator();
-
-      boost::optional<double> operator()( const double& frac, const int& i ) const {
-        double d = std::ldexp( frac, i );
-        if ( i > 6 ) {
-          info() << "Skipping " << d << endmsg;
-          return {};
-        }
-        info() << "Converting " << i << ", " << frac << " -> " << d << endmsg;
-        return d;
+      int    i;
+      double frac = std::frexp( d, &i );
+      info() << "Converting " << d << " -> " << frac << ", " << i << endmsg;
+      return std::make_tuple( frac, i );
+    }
+  };
+  DECLARE_COMPONENT( OptFrExpTransformer )
+
+  struct LdExpTransformer final
+      : Gaudi::Functional::ScalarTransformer<LdExpTransformer,
+                                             std::vector<double>( const std::vector<double>&, const std::vector<int>& ),
+                                             LegacyBaseClass_t> {
+    LdExpTransformer( const std::string& name, ISvcLocator* svcLoc )
+        : ScalarTransformer( name, svcLoc,
+                             {KeyValue{"InputFractions", {"/Event/MyVectorOfFractions"}},
+                              KeyValue{"InputIntegers", {"/Event/MyVectorOfIntegers"}}},
+                             {KeyValue{"OutputDoubles", {"/Event/MyNewVectorOfDoubles"}}} ) {}
+
+    using ScalarTransformer::operator();
+
+    double operator()( double frac, int i ) const {
+      double d = std::ldexp( frac, i );
+      info() << "Converting " << i << ", " << frac << " -> " << d << endmsg;
+      return d;
+    }
+  };
+  DECLARE_COMPONENT( LdExpTransformer )
+
+  struct OptLdExpTransformer final
+      : Gaudi::Functional::ScalarTransformer<OptLdExpTransformer,
+                                             std::vector<double>( const std::vector<double>&, const std::vector<int>& ),
+                                             LegacyBaseClass_t> {
+    OptLdExpTransformer( const std::string& name, ISvcLocator* svcLoc )
+        : ScalarTransformer( name, svcLoc,
+                             {KeyValue{"InputFractions", {"/Event/MyVectorOfFractions"}},
+                              KeyValue{"InputIntegers", {"/Event/MyVectorOfIntegers"}}},
+                             {KeyValue{"OutputDoubles", {"/Event/MyOptVectorOfDoubles"}}} ) {}
+
+    using ScalarTransformer::operator();
+
+    boost::optional<double> operator()( const double& frac, const int& i ) const {
+      double d = std::ldexp( frac, i );
+      if ( i > 6 ) {
+        info() << "Skipping " << d << endmsg;
+        return {};
       }
-    };
-    DECLARE_COMPONENT( OptLdExpTransformer )
+      info() << "Converting " << i << ", " << frac << " -> " << d << endmsg;
+      return d;
+    }
+  };
+  DECLARE_COMPONENT( OptLdExpTransformer )
 
-    struct VoidConsumer final : Gaudi::Functional::Consumer<void()> {
+  struct VoidConsumer final : Gaudi::Functional::Consumer<void(), BaseClass_t> {
 
-      using Consumer::Consumer;
+    using Consumer::Consumer;
 
-      void operator()() const override { info() << "executing VoidConsumer" << endmsg; }
-    };
+    void operator()() const override { info() << "executing VoidConsumer" << endmsg; }
+  };
 
-    DECLARE_COMPONENT( VoidConsumer )
-  } // namespace Examples
-} // namespace Gaudi
+  DECLARE_COMPONENT( VoidConsumer )
+} // namespace Gaudi::Examples