From f77f25f6c5bce49fe0a10e3ef8a05f41300a25d3 Mon Sep 17 00:00:00 2001
From: scott snyder <sss@karma>
Date: Thu, 13 Aug 2020 12:34:34 -0400
Subject: [PATCH] AthenaKernel: Add framework for compile-time iteration over
 bases.

Rework SG::Bases to (a) allow for an arbitrary number of base classes,
and (b) support compile-time iteration over the bases.

(This is working towards speeding up the casting operations performed
by DataProxy.)
---
 Control/AthenaKernel/AthenaKernel/BaseInfo.h  |  11 +-
 .../AthenaKernel/AthenaKernel/BaseInfo.icc    |  62 +----
 Control/AthenaKernel/AthenaKernel/Bases.h     | 214 ++++++++++++++++++
 Control/AthenaKernel/AthenaKernel/CondCont.h  |   1 +
 Control/AthenaKernel/CMakeLists.txt           |   4 +
 Control/AthenaKernel/share/Bases_test.ref     |   5 +
 Control/AthenaKernel/test/Bases_test.cxx      | 173 ++++++++++++++
 7 files changed, 404 insertions(+), 66 deletions(-)
 create mode 100644 Control/AthenaKernel/AthenaKernel/Bases.h
 create mode 100644 Control/AthenaKernel/share/Bases_test.ref
 create mode 100644 Control/AthenaKernel/test/Bases_test.cxx

diff --git a/Control/AthenaKernel/AthenaKernel/BaseInfo.h b/Control/AthenaKernel/AthenaKernel/BaseInfo.h
index 6c4121a2f7a5..dd2b38654b38 100755
--- a/Control/AthenaKernel/AthenaKernel/BaseInfo.h
+++ b/Control/AthenaKernel/AthenaKernel/BaseInfo.h
@@ -1,11 +1,7 @@
 // This file's extension implies that it's C, but it's really -*- C++ -*-.
-
 /*
-  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
 */
-
-// $Id: BaseInfo.h,v 1.11 2008-12-15 16:22:45 ssnyder Exp $
-
 /**
  * @file  AthenaKernel/BaseInfo.h
  * @author scott snyder
@@ -192,6 +188,7 @@
 #define ATHENAKERNEL_BASEINFO_H
 
 #include "CxxUtils/checker_macros.h"
+#include "AthenaKernel/Bases.h"
 #include "GaudiKernel/ClassID.h"
 #include <vector>
 #include <typeinfo>
@@ -246,6 +243,7 @@
       typedef NoBase Base2;      \
       typedef NoBase Base3;      \
       typedef NoBase Base4;      \
+      using bases = BaseList<B>; \
     };                           \
     template struct RegisterBaseInit<D >; \
     template struct BaseInit<D >; \
@@ -270,6 +268,7 @@
       typedef B2 Base2;          \
       typedef NoBase Base3;      \
       typedef NoBase Base4;      \
+      using bases = BaseList<B1,B2>;  \
     };                           \
     template struct RegisterBaseInit<D >; \
     template struct BaseInit<D >; \
@@ -295,6 +294,7 @@
       typedef B2 Base2;          \
       typedef B3 Base3;          \
       typedef NoBase Base4;          \
+      using bases = BaseList<B1,B2,B3>;           \
     };                           \
     template struct RegisterBaseInit<D >; \
     template struct BaseInit<D >; \
@@ -321,6 +321,7 @@
       typedef B2 Base2;          \
       typedef B3 Base3;          \
       typedef B4 Base4;          \
+      using bases = BaseList<B1,B2,B3,B4>;        \
     };                           \
     template struct RegisterBaseInit<D >; \
     template struct BaseInit<D >; \
diff --git a/Control/AthenaKernel/AthenaKernel/BaseInfo.icc b/Control/AthenaKernel/AthenaKernel/BaseInfo.icc
index 6e41ff105d9e..5fd344de4068 100755
--- a/Control/AthenaKernel/AthenaKernel/BaseInfo.icc
+++ b/Control/AthenaKernel/AthenaKernel/BaseInfo.icc
@@ -1,8 +1,6 @@
 /*
-  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
 */
-
-// $Id: BaseInfo.icc,v 1.9 2008-12-15 15:12:39 ssnyder Exp $
 /**
  * @file  AthenaKernel/BaseInfo.icc
  * @author scott snyder
@@ -19,64 +17,6 @@
 namespace SG {
 
 
-//===========================================================================
-// Inheritance data representation classes.
-//
-
-
-/**
- * @brief Marker to indicate a nonexistent base class.
- */
-struct NoBase {};
-
-
-/**
- * @brief Wrapper to indicate virtual derivation.
- *        @a Virtual<T> will mean that derivation from @a T is virtual.
- */
-template <class T> struct Virtual {};
-
-
-/**
- * @brief Traits class to hold derivation information for up to three
- *        base classes.
- *
- * @a Base1, etc., get typedef'd to the appropriate
- * base classes.  Use @a NoBase if there is no base class; use
- * @a Virtual<T> to indicate virtual derivation.  This class should
- * be specialized for every class @a T for which we want to know
- * inheritance information.
- */
-template <class T>
-struct Bases
-{
-  typedef NoBase Base1;
-  typedef NoBase Base2;
-  typedef NoBase Base3;
-  typedef NoBase Base4;
-};
-
-
-// Helper metafunction to get base class types.
-// Generic case.
-template <class T>
-struct BaseType
-{
-  typedef T type;
-  typedef std::false_type is_virtual;
-};
-
-
-// Helper metafunction to get base class types.
-// Virtual derivation case.
-template <class T>
-struct BaseType<Virtual<T> >
-{
-  typedef T type;
-  typedef std::true_type is_virtual;
-};
-
-
 //===========================================================================
 // Internal implementation class for @a BaseInfo.
 // This is used as a singleton, and should be accessed using the @a BaseInfo
diff --git a/Control/AthenaKernel/AthenaKernel/Bases.h b/Control/AthenaKernel/AthenaKernel/Bases.h
new file mode 100644
index 000000000000..1de6fe6b2d41
--- /dev/null
+++ b/Control/AthenaKernel/AthenaKernel/Bases.h
@@ -0,0 +1,214 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file AthenaKernel/Bases.h
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Aug, 2020
+ * @brief Traits class for representing derivation.
+ *
+ * For a class @c T, its base classes are available via the traits
+ * class defined here, as @c Bases<T>::bases.  This will be an instantiation
+ * of @c BaseList, defined below, which provides a @c foreach function
+ * to iterate over the base classes.
+ *
+ * For this to work, the @c Bases class must be specialized for each
+ * class @c T.  This is usually done using the @c SG_BASE macros
+ * defined in BaseInfo.h.
+ */
+
+
+#ifndef ATHENAKERNEL_BASES_H
+#define ATHENAKERNEL_BASES_H
+
+
+#include <type_traits>
+
+
+namespace SG {
+
+
+/**
+ * @brief Wrapper to indicate virtual derivation.
+ *        @a Virtual<T> will mean that derivation from @a T is virtual.
+ */
+template <class T> struct Virtual {};
+
+
+// Helper metafunction to get base class types.
+// Generic case.
+template <class T>
+struct BaseType
+{
+  typedef T type;
+  typedef std::false_type is_virtual;
+};
+
+
+// Helper metafunction to get base class types.
+// Virtual derivation case.
+template <class T>
+struct BaseType<Virtual<T> >
+{
+  typedef T type;
+  typedef std::true_type is_virtual;
+};
+
+
+/**
+ * @brief Represent a list of base classes.
+ *
+ * This generic definition is used only for the case of an empty
+ * base class list; all others use the specialization below.
+ */
+template <class... T>
+struct BaseList
+{
+public:
+  /**
+   * @brief Iterate over base classes.
+   * @param f Object to call for each base class.
+   * @param is_virtual True if we got to this base via virtual derivation.
+   *
+   * This is the specialization for the case of no base classes.
+   * See the version below for full documentation.
+   */
+  template <class CALLABLE>
+  static
+  auto foreach (CALLABLE f, bool /*is_virtual*/ = false)
+  {
+    // Need to find the return type.
+    using return_t = decltype (f (static_cast<void*> (nullptr), false));
+    return return_t (false);
+  }
+};
+
+
+/**
+ * @brief Marker to indicate a nonexistent base class.
+ */
+struct NoBase {}; // Temporary for back-compatibility.
+
+
+/**
+ * @brief Traits class to hold derivation information.
+ *
+ * The @a bases typedef gives a @c BaseList which holds the list
+ * of base classes.  The entries in this list may be of the
+ * form @a Virtual<T> to indicate virtual derivation.  This class should
+ * be specialized for every class @a T for which we want to know
+ * inheritance information.
+ */
+template <class T>
+struct Bases
+{
+public:
+  using bases = BaseList<>;
+
+  typedef NoBase Base1; // Temporary for back-compatibility.
+  typedef NoBase Base2;
+  typedef NoBase Base3;
+  typedef NoBase Base4;
+};
+
+
+/**
+ * @brief Represent a non-empty list of base classes.
+ */
+template <class BASE, class... REST>
+struct BaseList<BASE, REST...>
+{
+public:
+  /**
+   * @brief Iterate over base classes.
+   * @param f Object to call for each base class.
+   * @param is_virtual True if we got to this base via virtual derivation.
+   *
+   * @c f should be a callable object.  It gets called for each base class
+   * like this:
+   *
+   *@code
+   *    auto ret = f (static_cast<T*> (nullptr), is_virtual);
+   @endcode
+   *
+   * Doing it like this allows using a generic lambda.
+   *
+   * When called from @c Bases<D>::bases, note that the class @c D itself
+   * is not included in the iteration.  You should also not assume
+   * anything about the order in which the bases are visited.
+   *
+   * The return type is user-defined, except that it must be convertable
+   * to and from a bool.  If @f returns a value that converts to a
+   * @c true boolean value, then the iteration stops at that point,
+   * and that value is returned.  It must also be possible to initialize
+   * the return type from a @c false boolean value.  This allows, for example,
+   * a pointer to be used for the return type, with the iteration stopping
+   * once a non-null pointer is returned.
+   *
+   * It must also be possible to instantiate @c f with a @c void* argument.
+   * (This will not actually be called, but it must be possible to
+   * instantiate it in order to find the return type.)
+   *
+   * The iteration generated here is fixed at compile time.
+   * It does NOT take into account any information added by @c SG_ADD_BASE.
+   *
+   * Example:
+   *
+   *@code
+   *  // Does T derive from the class described by ti?
+   *  template <class T>
+   *  bool derivesFrom (const std::type_info& ti)
+   *  {
+   *    // Check if T itself is ti.
+   *    if (typeid(T) == ti) return true;
+   *
+   *    auto search = [&] (auto* p, bool is_virtual [[maybe_unused]])
+   *    {
+   *       using base_t = std::remove_pointer_t<decltype(p)>;
+   *       if (typeid(base_t) == ti) return true;
+   *       return false;
+   *    };
+   *    return SG::Bases<T>::bases::foreach (search);
+   *  }
+   @endcode
+   */
+  template <class CALLABLE>
+  static
+  auto foreach (CALLABLE f, bool is_virtual = false)
+  {
+    // Unwrap a SG::Virtual if needed.
+    using base_t = typename BaseType<BASE>::type;
+    const bool base_is_virtual = is_virtual || BaseType<BASE>::is_virtual::value;
+
+    // Call the function on our first base.
+    auto ret1 = f (static_cast<base_t*> (nullptr), base_is_virtual);
+    if (static_cast<bool> (ret1)) {
+      return ret1;
+    }
+
+    // Call it on bases of our first base.
+    auto ret2 = Bases<base_t>::bases::foreach (f, base_is_virtual);
+    if (static_cast<bool> (ret2)) {
+      return ret2;
+    }
+
+    // Call it on our remaining bases.
+    if constexpr (sizeof... (REST) > 0) {
+      auto ret3 = BaseList<REST...>::foreach (f, is_virtual);
+      if (static_cast<bool> (ret3)) {
+        return ret3;
+      }
+    }
+
+    // Got to the end; return false;
+    using return_t = decltype (ret1);
+    return return_t (false);
+  }
+};
+
+
+} // namespace SG
+
+
+#endif // not ATHENAKERNEL_BASES_H
diff --git a/Control/AthenaKernel/AthenaKernel/CondCont.h b/Control/AthenaKernel/AthenaKernel/CondCont.h
index b2759dc66b1b..3281debb7ca3 100644
--- a/Control/AthenaKernel/AthenaKernel/CondCont.h
+++ b/Control/AthenaKernel/AthenaKernel/CondCont.h
@@ -796,6 +796,7 @@ namespace SG {
 template <typename T>
 struct Bases<CondCont<T> >
 {
+  using bases = BaseList<CondContBase>;
   typedef CondContBase Base1;               
   typedef NoBase Base2;          
   typedef NoBase Base3;      
diff --git a/Control/AthenaKernel/CMakeLists.txt b/Control/AthenaKernel/CMakeLists.txt
index 73b43c701209..7feb5914ff98 100644
--- a/Control/AthenaKernel/CMakeLists.txt
+++ b/Control/AthenaKernel/CMakeLists.txt
@@ -97,6 +97,10 @@ atlas_add_test( safe_clid_test
    SOURCES test/safe_clid_test.cxx
    LINK_LIBRARIES AthenaKernel )
 
+atlas_add_test( Bases_test
+   SOURCES test/Bases_test.cxx
+   LINK_LIBRARIES AthenaKernel )
+
 atlas_add_test( BaseInfo_test
    SOURCES test/BaseInfo_test.cxx
    LINK_LIBRARIES AthenaKernel )
diff --git a/Control/AthenaKernel/share/Bases_test.ref b/Control/AthenaKernel/share/Bases_test.ref
new file mode 100644
index 000000000000..e41703d20229
--- /dev/null
+++ b/Control/AthenaKernel/share/Bases_test.ref
@@ -0,0 +1,5 @@
+AthenaKernel/Bases_test
+test1
+test2
+1
+0
diff --git a/Control/AthenaKernel/test/Bases_test.cxx b/Control/AthenaKernel/test/Bases_test.cxx
new file mode 100644
index 000000000000..21788cd4c1c8
--- /dev/null
+++ b/Control/AthenaKernel/test/Bases_test.cxx
@@ -0,0 +1,173 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+/**
+ * @file  AthenaKernel/test/Bases_test.cxx
+ * @author scott snyder
+ * @date Aug, 2020
+ * @brief Regression test for Bases.
+ */
+
+
+#undef NDEBUG
+
+#include "AthenaKernel/Bases.h"
+#include <type_traits>
+#include <typeinfo>
+#include <string>
+#include <vector>
+#include <iostream>
+#include <cassert>
+
+
+class A
+{
+public:
+  static const char* name() { return "A"; }
+};
+class B
+{
+public:
+  static const char* name() { return "B"; }
+};
+class C : public A, public B
+{
+public:
+  static const char* name() { return "C"; }
+};
+
+
+class D : public C
+{
+public:
+  static const char* name() { return "C"; }
+};
+
+class F
+{
+public:
+  static const char* name() { return "F"; }
+};
+
+class E : virtual public C, public F
+{
+public:
+  static const char* name() { return "E"; }
+};
+
+
+template <class T>
+struct name
+{
+  static const char* value() { return T::name(); }
+};
+template <>
+struct name<void>
+{
+  static const char* value() { return "void"; }
+};
+
+
+namespace SG {
+
+template <>
+class Bases<C>
+{
+public:
+  using bases = BaseList<A, B>;
+};
+
+
+template <>
+class Bases<D>
+{
+public:
+  using bases = BaseList<C>;
+};
+
+template <>
+class Bases<E>
+{
+public:
+  using bases = BaseList<Virtual<C>, F>;
+};
+
+} // namespace SG
+
+
+void test1()
+{
+  std::cout << "test1\n";
+
+  std::vector<std::string> names;
+
+  auto collect_names = [&] (auto* p, bool is_virtual)
+  {
+    using base_t = std::remove_pointer_t<decltype(p)>;
+    names.push_back (name<base_t>::value());
+    if (is_virtual) names.push_back ("(virtual)");
+    return false;
+  };
+
+  assert (SG::Bases<C>::bases::foreach (collect_names) == false);
+  assert (names == (std::vector<std::string> { "A", "B" }));
+
+  names.clear();
+  assert (SG::Bases<D>::bases::foreach (collect_names) == false);
+  assert (names == (std::vector<std::string> { "C", "A", "B" }));
+
+  names.clear();
+  assert (SG::Bases<E>::bases::foreach (collect_names) == false);
+  assert (names == (std::vector<std::string> { "C", "(virtual)",
+                                                "A", "(virtual)",
+                                                "B", "(virtual)",
+                                                "F"}));
+
+  auto collect_names2 = [&] (auto* p, bool is_virtual)
+  {
+    using base_t = std::remove_pointer_t<decltype(p)>;
+    const char* nm = name<base_t>::value();
+    names.push_back (nm);
+    if (is_virtual) names.push_back ("(virtual)");
+    if (std::string(nm) == "A") return nm;
+    return static_cast<const char*> (nullptr);
+  };
+
+  names.clear();
+  const char* nm = SG::Bases<D>::bases::foreach (collect_names2);
+  assert (std::string(nm) == "A");
+  assert (names == (std::vector<std::string> { "C", "A" }));
+}
+
+
+template <class T>
+bool derivesFrom (const std::type_info& ti)
+{
+  // Check if T itself is ti.
+  if (typeid(T) == ti) return true;
+
+  auto search = [&] (auto* p, bool is_virtual [[maybe_unused]])
+  {
+    using base_t = std::remove_pointer_t<decltype(p)>;
+    if (typeid(base_t) == ti) return true;
+    return false;
+  };
+  return SG::Bases<T>::bases::foreach (search);
+}
+
+
+void test2()
+{
+  std::cout << "test2\n";
+  std::cout << derivesFrom<C> (typeid(A)) << "\n";
+  std::cout << derivesFrom<C> (typeid(D)) << "\n";
+}
+
+
+int main()
+{
+  std::cout << "AthenaKernel/Bases_test\n";
+  test1();
+  test2();
+  return 0;
+}
-- 
GitLab