|
||||
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
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |