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