diff --git a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/BenchmarkTools.hpp b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/BenchmarkTools.hpp
index 8581d340e4fa7f14c6bf21be1faf0870b4b97689..42964bf2fd321aef6a17e091ec57e68df91bf8c4 100644
--- a/Tests/CommonHelpers/Acts/Tests/CommonHelpers/BenchmarkTools.hpp
+++ b/Tests/CommonHelpers/Acts/Tests/CommonHelpers/BenchmarkTools.hpp
@@ -19,6 +19,8 @@
 #include <utility>
 #include <vector>
 
+#include "Acts/Utilities/TypeTraits.hpp"
+
 namespace Acts {
 namespace Test {
 
@@ -348,45 +350,41 @@ struct MicroBenchmarkIterImpl<Callable, void, void> {
   }
 };
 
-// Trick to have a static_assert fire only when the base case of a template
-// specialization chain is instantiated
-template <typename T>
-constexpr bool always_false = false;
-
-// Mechanism to type-check that Callable has the right signature, avoiding
-// crazy compiler errors deep down in the implementation.
-template <typename Callable, typename Input = void, typename Enable = void>
-struct MicroBenchmarkIter {
-  // Hitting the base case is an error
-  static_assert(always_false<Callable>,
-                "Bad benchmark iteration function signature");
+template <typename T, typename I>
+using call_with_input_t = decltype(std::declval<T>()(std::declval<I>()));
 
-  // Still provide a method with expected signature so that the above
-  // static_assert error is the only compiler error that gets emitted.
-  static inline void iter(const Callable&, const Input* = nullptr) {}
-};
+template <typename T>
+using call_without_input_t = decltype(std::declval<T>()());
 
 // If callable is a callable that takes the expected input argument type, then
 // this specialization will be selected...
-template <typename Callable, typename Input>
-struct MicroBenchmarkIter<
-    Callable, Input, std::enable_if_t<std::is_invocable_v<Callable, Input>>> {
-  // ...so we can safely call invoke_result_t here
+template <typename Callable, typename Input = void>
+struct MicroBenchmarkIter {
+  constexpr static bool is_callable =
+      concept ::exists<call_with_input_t, Callable, Input>;
   static inline void iter(const Callable& iteration, const Input* input) {
-    using Result = std::invoke_result_t<Callable, const Input&>;
-    MicroBenchmarkIterImpl<Callable, Input, Result>::iter(iteration, *input);
+    static_assert(is_callable,
+                  "Gave callable that is not callable without input");
+    if constexpr (is_callable) {
+      using Result = std::invoke_result_t<Callable, const Input&>;
+      MicroBenchmarkIterImpl<Callable, Input, Result>::iter(iteration, *input);
+    }
   }
 };
 
 // If Callable is a callable that takes no argument, this specialization will be
 // picked instead of the one above...
 template <typename Callable>
-struct MicroBenchmarkIter<Callable, void,
-                          std::enable_if_t<std::is_invocable_v<Callable>>> {
-  // ...so we can safely call invoke_result_t here
+struct MicroBenchmarkIter<Callable, void> {
+  constexpr static bool is_callable =
+      concept ::exists<call_without_input_t, Callable>;
+
   static inline void iter(const Callable& iteration, const void* = nullptr) {
-    using Result = std::invoke_result_t<Callable>;
-    MicroBenchmarkIterImpl<Callable, void, Result>::iter(iteration);
+    static_assert(is_callable, "Gave callable that is not callable with input");
+    if constexpr (is_callable) {
+      using Result = std::invoke_result_t<Callable>;
+      MicroBenchmarkIterImpl<Callable, void, Result>::iter(iteration);
+    }
   }
 };
 
@@ -472,6 +470,7 @@ MicroBenchmarkResult microBenchmarkImpl(Callable&& run, size_t iters_per_run,
 //         (which is a good approximation of the elapsed time).
 //       * Note after how much elapsed time the timings typically become steady.
 //
+
 template <typename Callable>
 MicroBenchmarkResult microBenchmark(
     Callable&& iteration, size_t iters_per_run, size_t num_runs = 20000,