Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-19 09:23:42

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2019 CERN for the benefit of the Acts project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include <functional>
0012 #include <string>
0013 #include <type_traits>
0014 
0015 namespace Acts {
0016 
0017 namespace detail {
0018 
0019 /// This file contains an implementation of the detection idiom in C++.
0020 /// It's not currently in the standard, but can be implemented using
0021 /// standard features. This implementation is largely taken from the C++
0022 /// [technical specifications, library fundamentals
0023 /// v2](https://en.cppreference.com/w/cpp/experimental/is_detected)
0024 ///
0025 /// The detector pattern works like this: there is a default type, that
0026 /// accepts an "operation" that can be basically anything. It also accepts
0027 /// variadic arguments for that operation. The default type
0028 /// has a member type that is std::false_type to indicate success or
0029 /// failure. It also has a member type "type" which captures a type result.
0030 /// Then there is a specialization which attempts to instantiate the operation
0031 /// with the given parameters, and tries to assign it into std::void_t. If the
0032 /// operation fails to instantiate (say, the checked for type does not exist),
0033 /// the specialization will not be instantiated, and the compiler falls back to
0034 /// the default type which contains std::false_type. Since it happens inside
0035 /// while the compiler tries to find a better matching template specialization
0036 /// than the default one (so basically overload resolution), a compile error
0037 /// inside the operation is handled as a substitution failure, and is not an
0038 /// error. If the instantiation succeeds, the specialization contains a
0039 /// std::true_type, and an alias to the result of the operation.
0040 ///
0041 /// Essentially, it provides a convenient way to "lift" operations into this
0042 /// overload resolution, allowing testing expressions and evaluating them into
0043 /// compile time booleans (instead of compilation failures).
0044 
0045 /// Helper struct which cannot be constructed (or destroyed) at all.
0046 struct nonesuch {
0047   ~nonesuch() = delete;
0048   nonesuch(nonesuch const&) = delete;
0049   void operator=(nonesuch const&) = delete;
0050 };
0051 
0052 /// This is the default specialization.
0053 /// It does not attempt to instantiate ``Op<Args...>`` at all.
0054 /// @tparam Default The default type to set
0055 /// @tparam AlwaysVoid Helper type that accepts the void instantiation
0056 /// @tparam Op The operation to test
0057 /// @tparam Args Arguments to the operation
0058 template <class Default, class AlwaysVoid, template <class...> class Op,
0059           class... Args>
0060 struct detector {
0061   using value_t = std::false_type;
0062   using type = Default;
0063 };
0064 
0065 /// This is the specialization which attempts to instantiate ``Op<Args...``.
0066 /// @tparam Default Default type to set if substitution fails
0067 /// @tparam Op The operation to test
0068 /// @tparam Args Arguments to the operation
0069 template <class Default, template <class...> class Op, class... Args>
0070 struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
0071   // Note that std::void_t is a C++17 feature
0072   using value_t = std::true_type;
0073   using type = Op<Args...>;
0074 };
0075 
0076 }  // namespace detail
0077 
0078 namespace Concepts {
0079 
0080 /// This type ties together the detection idiom. It instantiates the
0081 /// ``detector`` template with the ``Op`` and ``Args`` and resolves to the exact
0082 /// value type. In essence, if ``Op<Args...>`` succeeds, this will evaluate to
0083 /// ``std::true_type``, and if not, it will evaluate to ``std::false_type``.
0084 /// @tparam Op The operation to test
0085 /// @tparam Args The arguments to the operation
0086 template <template <class...> class Op, class... Args>
0087 using is_detected =
0088     typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
0089 
0090 /// This type calls into the detector (same as ``is_detected``) but it extracts
0091 /// the return type of ``Op<Args...>``.
0092 /// @tparam Op The operation
0093 /// @tparam Args The arguments to the operation
0094 template <template <class...> class Op, class... Args>
0095 using detected_t =
0096     typename detail::detector<detail::nonesuch, void, Op, Args...>::type;
0097 
0098 /// This invokes ``detected_t``, and checks whether its result matches
0099 /// ``Expected``.
0100 /// @tparam Expected The expected result of the operation.
0101 /// @tparam Op The operation
0102 /// @tparam Args The arguments to the operation
0103 template <class Expected, template <class...> class Op, class... Args>
0104 using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
0105 
0106 /// This evaluates ``Op`` inside the detector, and checks whether the resolved
0107 /// type
0108 /// is convertible to ``To``.
0109 /// @tparam To The type to check convertibility to.
0110 /// @tparam Op The operation
0111 /// @tparam Args The arguments to the operation
0112 template <class To, template <class...> class Op, class... Args>
0113 using is_detected_convertible =
0114     std::is_convertible<detected_t<Op, Args...>, To>;
0115 
0116 /// Helper which invokes the detector with a default type, and resolves to the
0117 /// type.
0118 /// @tparam Default The type to resolve to if ``Op<Args...>`` does not resolve.
0119 /// @tparam Op The operation
0120 /// @tparam Args The argument to the operation
0121 template <class Default, template <class...> class Op, class... Args>
0122 using detected_or = detail::detector<Default, void, Op, Args...>;
0123 
0124 /// Define some sort of "Domain Specific Language" to declare concepts a little
0125 /// more naturally. These are taken from
0126 /// https://izzys.casa/2016/09/implementing-concepts-in-cxx/
0127 
0128 /// Helper which combines a set of predicates (constexpr bools) with a logical
0129 /// AND. Converts to ``std::bool_constant``.
0130 /// @tparam Bs The booleans to combine
0131 template <bool... Bs>
0132 constexpr bool require = std::conjunction<std::bool_constant<Bs>...>::value;
0133 
0134 /// Helper which forms the logical OR of its arguments.
0135 /// Converts to ``std::bool_constant``.
0136 /// @tparam Bs The booleans to combine.
0137 template <bool... Bs>
0138 constexpr bool either = std::disjunction<std::bool_constant<Bs>...>::value;
0139 
0140 /// Alias for the negation of a ``require``. This is essentially a NOT ANY test.
0141 /// @tparam Bs The booleans.
0142 template <bool... Bs>
0143 constexpr bool disallow = !require<Bs...>;
0144 
0145 /// Alias to ``is_detected`` which unpacks the constexpr boolean value.
0146 /// @tparam Op The operation
0147 /// @tparam Args The arguments to the operation.
0148 template <template <class...> class Op, class... Args>
0149 constexpr bool exists = is_detected<Op, Args...>::value;
0150 
0151 /// Alias to conversion check, which also extracts the constexpr boolean value.
0152 /// @tparam To The type to check convertibility to.
0153 /// @tparam Op The operation
0154 /// @tparam Args The arguments to the operation.
0155 template <class To, template <class...> class Op, class... Args>
0156 constexpr bool converts_to = is_detected_convertible<To, Op, Args...>::value;
0157 
0158 /// Unpacks the constexpr boolean value from ``is_detected_exact``
0159 /// @tparam Exact The type to check identity against
0160 /// @tparam Op The operation
0161 /// @tparam Args The arguments to the operation.
0162 template <class Exact, template <class...> class Op, class... Args>
0163 constexpr bool identical_to = is_detected_exact<Exact, Op, Args...>::value;
0164 
0165 /// Helper which evaluates whether the type ``T`` has a method with a given
0166 /// signature.
0167 /// @tparam T The type to check on. This can contain a const qualifier if you
0168 /// want to check on that.
0169 /// @tparam R The return type
0170 /// @tparam M The method trait, as generated by METHOD_TRAIT
0171 /// @tparam Arguments The argument types that make up the signature.
0172 template <typename T, typename R, template <class...> class M,
0173           typename... Arguments>
0174 constexpr bool has_method = M<T, R, Arguments...>::template tv<T>::value;
0175 
0176 /// Helper to assert if a member of a given type exists. Basically only calls
0177 /// into ``identical_to`` but is nicer to read.
0178 /// @tparam T The type to check existence of member on.
0179 /// @tparam M The member type trait
0180 /// @tparam V The type that the member is supposed to have.
0181 template <typename T, template <class...> class M, typename V>
0182 constexpr bool has_member = identical_to<V, M, T>;
0183 
0184 /// Have a look at ``TypeTraitsTest.cpp`` to see most of this in action.
0185 }  // namespace Concepts
0186 }  // namespace Acts
0187 
0188 /// These helpers allow writing checks. The missing piece is something that you
0189 /// can put into these. This is the point where this turns into type checking.
0190 /// ``Op`` from above can be anything. For instance, we can write an expression,
0191 /// and have the compiler try to calculate that expression's resulting type,
0192 /// like so:
0193 ///
0194 /// .. code-block:: cpp
0195 ///
0196 ///    decltype(std::declval<T>().member_a)
0197 ///
0198 /// ``std::declval<T>()`` constructs a **pseudo-value** of type ``T`` which
0199 /// works even if ``T`` is not actually constructible like this. Then we access
0200 /// a member called ``member_a`` inside and instruct the compiler to calculate
0201 /// the resulting type using ``decltype``. This will only work if the expression
0202 /// is valid. If not, this would normally cause a compilation error. If we wrap
0203 /// it into the
0204 /// **detection idiom**, however, we can turn that compilation error into a
0205 /// simple constexpr false!
0206 ///
0207 /// To do that, we need to put that expression above into a **metafunction**. If
0208 /// we simply wrote
0209 ///
0210 /// .. code-block:: cpp
0211 ///
0212 ///    is_detected<decltype(std::declval<T>().member_a)>
0213 ///
0214 /// where ``decltype(std::declval<T>().member_a)`` is ``Op``, and ``Args`` is
0215 /// empty, we still get a compilation error. This is because the compiler
0216 /// evaluates the first argument before even passing it into ``is_detected``,
0217 /// and that means
0218 /// **outside** the overload resolution context. This is why we need to pass a
0219 /// metafunction around the expression as ``Op``, and the concrete type ``T`` as
0220 /// ``Args`` so that the actual evaluation of the expression happens **inside
0221 /// the overload resolution context**. Luckily, writing a metafunction around
0222 /// the expression is as easy as
0223 ///
0224 /// .. code-block:: cpp
0225 ///
0226 ///    template <typename T>
0227 ///    using member_a_t = decltype(std::declval<T>().member_a>);
0228 ///
0229 /// and we can then use it like ``is_detected<member_a_t, T>``.
0230 ///
0231 /// Basically, what you have to do to write type assertions using this pattern
0232 /// is metafunctions like the one described above. Those can be member checks,
0233 /// like shown before, nested type checks like
0234 ///
0235 /// .. code-block:: cpp
0236 ///
0237 ///    template <typename T>
0238 ///    using nested_a_t = typename T::NestedA;
0239 ///
0240 /// and checks for contained templates like
0241 ///
0242 /// .. code-block:: cpp
0243 ///
0244 ///    template <typename T>
0245 ///    using meta_t = typename T::template meta<void, void>;
0246 ///
0247 /// but also arbitrary expressions, like operators, or even operators between
0248 /// types. For instance, say we have two types ``U`` and ``V``. We can then
0249 /// write a metafunction that attempts to instantiate a binary operation between
0250 /// the two:
0251 ///
0252 /// .. code-block:: cpp
0253 ///
0254 ///    template <typename U, typename V>
0255 ///    using binary_op_t = decltype(std::declval<U>() + std::declval<V>());
0256 ///
0257 /// and simply takes both ``U`` and ``V`` as meta-arguments.
0258 /// Invoked like ``is_detected<binary_op_t, TypeA, TypeB>`` on some types
0259 /// ``TypeA`` and ``TypeB``, this will tell you whether you can call that binary
0260 /// operation on values of those types.
0261 ///
0262 /// Implementing method checks is a little more involved. A simple method check
0263 /// can be written like
0264 ///
0265 /// .. code-block:: cpp
0266 ///
0267 ///    template <typename T>
0268 ///    using foo_method_t = decltype(std::declval<T>().foo(double, int)>);
0269 ///
0270 /// This only checks if the given expression is valid. That means this
0271 /// will evaluate to true even if the actual arguments of that function are
0272 /// references, as the compiler will figure that out behind the scenes and still
0273 /// allow you to call it. That can be fine, if you're really only interested if
0274 /// that specific call is allowed. Remember
0275 /// that ``decltype`` calculates the type of the expression. In this context, it
0276 /// will evaluate to **the return type** of the called function. Using
0277 /// ``identical_to`` you can therefore assert the return type to be a given
0278 /// value.
0279 ///
0280 /// You might, however, want to constrain the *exact* types of the arguments,
0281 /// and the method's const qualifier. This is less straightforward. To achieve
0282 /// this, a macro is provided below, which will generate the required code for a
0283 /// trait predicate. You *cannot* use the result of this macro directly with the
0284 /// helpers defined above. What the macro provides is a metafunction, which
0285 /// still takes the type, the return type and the arguments as metaarguments. So
0286 /// the result of the macro only encodes the name of the method, not the rest of
0287 /// its signature. That means you can use the same method trait to check for any
0288 /// signature involving the same method name. Say you generated
0289 ///
0290 /// .. code-block:: cpp
0291 ///
0292 ///    // foo_method_t is the name of the struct generated by this macro,
0293 ///    // foo is the name of the method you want to check for.
0294 ///    METHOD_TRAIT(foo_method_t, foo);
0295 ///
0296 /// An additional helper ``has_method`` is provided,
0297 /// which wraps a little boilerplate to check a specific signature.
0298 /// You can then write
0299 ///
0300 /// .. code-block:: cpp
0301 ///
0302 ///    has_method<T, R, foo_method_t, bool, const int&>
0303 ///
0304 /// to check for a signature of the form
0305 ///
0306 /// .. code-block:: cpp
0307 ///
0308 ///    R T::foo(bool, const int&)
0309 ///
0310 /// If you want to check for a const method you can modify this to
0311 ///
0312 /// .. code-block:: cpp
0313 ///
0314 ///    has_method<const T, R, foo_method_t, bool, const int&>
0315 ///
0316 /// Note that both will only evaluate to true if the const qualifier matches
0317 /// what you gave exactly. If you want to check for a method of a given
0318 /// specifier and you don't care if the method is const or not, you have to
0319 /// write out both variants explicitly, and combine them with ``either``.
0320 
0321 /// @cond
0322 
0323 /// This macro generates some boilerplate code that is necessary to correctly
0324 /// implement a method type trait that evaluates to constexpr bools correctly
0325 /// @param trait_name The resulting name of the trait.
0326 /// @param method_name The name of the method the trait shall check.
0327 #define METHOD_TRAIT(trait_name, method_name)                                  \
0328   template <class T, typename R, typename... Arguments>                        \
0329   struct trait_name {                                                          \
0330     /* Meta function to check if a type has a const qualifier*/                \
0331     /* (by stripping it and seeing if something changed */                     \
0332     template <typename T_>                                                     \
0333     static constexpr bool is_const =                                           \
0334         !std::is_same_v<std::remove_const_t<T_>, T_>;                          \
0335                                                                                \
0336     /*These following meta-functions basically to this: they check whether or  \
0337      * not the actual function pointer extracted through ``&T::method_name``   \
0338      * can                                                                     \
0339      * be assigned to a prepared function pointer type with the given          \
0340      * signature. This checks the exact signature, and not just callability    \
0341      * and validity of the expression. */                                      \
0342                                                                                \
0343     /* Meta function which constructs the right type to check a function       \
0344      * pointer, non-const version*/                                            \
0345     template <typename T_, typename = int>                                     \
0346     struct fptr_meta {                                                         \
0347       template <typename... Arguments_>                                        \
0348       using type = typename std::integral_constant<                            \
0349           decltype(std::declval<T_>().method_name(                             \
0350               std::declval<Arguments_>()...)) (T_::*)(Arguments_...),          \
0351           &T_::method_name>::value_type;                                       \
0352     };                                                                         \
0353                                                                                \
0354     /* Meta function which constructs the right type to check a function       \
0355      * pointer, const version*/                                                \
0356     /* The ``const`` needs to be put in there in one specific spot, that's why \
0357      * the metafunction is needed*/                                            \
0358     template <typename T_>                                                     \
0359     struct fptr_meta<T_, std::enable_if_t<is_const<T_>, int>> {                \
0360       template <typename... Arguments_>                                        \
0361       using type = typename std::integral_constant<                            \
0362           decltype(std::declval<T_>().method_name(                             \
0363               std::declval<Arguments_>()...)) (T_::*)(Arguments_...) const,    \
0364           &T_::method_name>::value_type;                                       \
0365     };                                                                         \
0366                                                                                \
0367     /* Helper on top of the function pointer metafunction */                   \
0368     template <typename T_, typename... Arguments_>                             \
0369     using fptr_meta_t = typename fptr_meta<T_>::template type<Arguments_...>;  \
0370                                                                                \
0371     /* Trait check for the qualifier and the return type of the function */    \
0372     /* This does not check the const qualifier at all */                       \
0373     template <typename T_, typename... Arguments_>                             \
0374     using qual_ret = decltype(std::declval<T_>().method_name(                  \
0375         std::declval<Arguments_>()...));                                       \
0376                                                                                \
0377     /* The problem is this: while the above is fine with and without const,    \
0378      * and with and without exact argument type match, the assignment to the   \
0379      * function pointer fails hard if there is no method at all with the given \
0380      * name. That is undesirable. The following first uses the expression      \
0381      * validity check to assert that there is in fact a method of the given    \
0382      * name, and only if that is the case, try to compile the function pointer \
0383      * based signature check. That way, there is no hard failures, only        \
0384      * substitution failures and we're happy. */                               \
0385     template <typename T_, typename = int>                                     \
0386     struct tv {                                                                \
0387       static constexpr bool value = false;                                     \
0388     };                                                                         \
0389     template <typename T_>                                                     \
0390     struct tv<T_, std::enable_if_t<Acts::Concepts::is_detected_exact<          \
0391                                        R, qual_ret, T_, Arguments...>::value,  \
0392                                    int>> {                                     \
0393       /* This is only ever evaluate if the method exists!*/                    \
0394       static constexpr bool value =                                            \
0395           Acts::Concepts::is_detected<fptr_meta_t, T, Arguments...>::value;    \
0396     };                                                                         \
0397   }
0398 
0399 /// @endcond