diff --git a/CMakeLists.txt b/CMakeLists.txt index f5db15cdb677..5c6cd9b8b86f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1374,6 +1374,7 @@ target_precompile_headers(mixxx-lib PUBLIC src/track/tracknumbers.h src/track/trackrecord.h src/track/trackref.h + src/util/always_false.h src/util/alphabetafilter.h src/util/battery/battery.h src/util/cache.h @@ -1445,6 +1446,7 @@ target_precompile_headers(mixxx-lib PUBLIC src/util/path.h src/util/performancetimer.h src/util/platform.h + src/util/qstringformat.h src/util/qt.h src/util/quuid.h src/util/rampingvalue.h diff --git a/src/util/always_false.h b/src/util/always_false.h new file mode 100644 index 000000000000..b57794868c1b --- /dev/null +++ b/src/util/always_false.h @@ -0,0 +1,5 @@ +#pragma once +// always false, used for static_assert and workaround for compilers without +// https://cplusplus.github.io/CWG/issues/2518.html +template +static constexpr bool always_false_v = false; diff --git a/src/util/qstringformat.h b/src/util/qstringformat.h new file mode 100644 index 000000000000..885f035bb51d --- /dev/null +++ b/src/util/qstringformat.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#include "util/always_false.h" + +namespace { + +// taken from Qt +template +static constexpr bool is_convertible_to_view_or_qstring_v = + std::is_convertible_v || + std::is_convertible_v || + std::is_convertible_v; + +// check if we can call QString::number(T) with T +template +static constexpr bool is_number_compatible_v = + std::is_invocable_v()))(T), T>; +} // namespace + +// Try to convert T to a type that would be accepted by QString::args(Args&&...) +template +auto convertToQStringConvertible(T&& arg) { + if constexpr (is_convertible_to_view_or_qstring_v) { + // no need to do anything, just return verbatim + return std::forward(arg); + } else if constexpr (is_number_compatible_v) { + return QString::number(std::forward(arg)); + } else { + static_assert(always_false_v, "Unsupported type for QString::arg"); + // unreachable, but returning a QString results in a better error message + // because the log won't be spammed with all the QString::arg overloads + // it couldn't match with `void`. + return QString(); + } +} diff --git a/src/util/timer.h b/src/util/timer.h index 3118d218f9c0..d683ad22763f 100644 --- a/src/util/timer.h +++ b/src/util/timer.h @@ -39,40 +39,45 @@ class Timer { // TODO: replace with std::experimental::scope_exit once stabilized class ScopedTimer { public: - ScopedTimer(QStringView key, - Stat::ComputeFlags compute = kDefaultComputeFlags) - : ScopedTimer(key, QStringView(), compute) { - } - ScopedTimer(QStringView key, - int i, - Stat::ComputeFlags compute = kDefaultComputeFlags) - : ScopedTimer(key, - CmdlineArgs::Instance().getDeveloper() - ? QString::number(i) - : QStringView(), - compute) { - } - - ScopedTimer(QStringView key, QStringView arg, Stat::ComputeFlags compute = kDefaultComputeFlags) + // Allows the timer to contain a format string which is only assembled + // when we're not in `--developer` mode. + /// @param compute Flags to use for the Stat::ComputeFlags (can be omitted) + /// @param key The format string as QStringLiteral to identify the timer + /// @param args The arguments to pass to the format string + template + ScopedTimer(Stat::ComputeFlags compute, T&& key, Ts&&... args) : m_maybeTimer(std::nullopt) { + static_assert(std::is_same_v, + "only QString is supported as key type. Wrap it in u\"\"" + "_s or QStringLiteral() " + "to avoid runtime UTF-16 conversion."); if (!CmdlineArgs::Instance().getDeveloper()) { - return; + return; // leave timer in cancelled state } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QString strKey = arg.isEmpty() ? key.toString() : key.arg(arg); -#else - QString strKey = arg.isEmpty() ? key.toString() : key.toString().arg(arg); -#endif - m_maybeTimer = std::make_optional(std::move(strKey), compute); + // + auto assembledKey = QString(std::forward(key)); + // ensure the string was indeed a literal an not unnecessarily heap-allocated + DEBUG_ASSERT(assembledKey.capacity() == 0); + if constexpr (sizeof...(args) > 0) { + // only try to call QString::arg when we've been given parameters + assembledKey = assembledKey.arg(convertToQStringConvertible(std::forward(args))...); + } + m_maybeTimer = std::make_optional(assembledKey, compute); m_maybeTimer->start(); } + template + ScopedTimer(T&& key, Ts&&... args) + : ScopedTimer(kDefaultComputeFlags, std::forward(key), std::forward(args)...) { + } + ~ScopedTimer() noexcept { if (m_maybeTimer) { m_maybeTimer->elapsed(true); } } + // copying would technically be possible, but likely not intended ScopedTimer(const ScopedTimer&) = delete; ScopedTimer& operator=(const ScopedTimer&) = delete;