summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2021-01-16 12:54:43 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-01-22 09:27:32 +0000
commit332df3bdd7e6d5f7f9a09c9bd0861f5370bc667c (patch)
tree8e6186b4036e44ff2b1e1b80bc3ac32dca51d5d6
parent760f6bc37313888fa755235a61d9962624c688f4 (diff)
downloadmongo-332df3bdd7e6d5f7f9a09c9bd0861f5370bc667c.tar.gz
SERVER-47709 add deduceChronoDuration to duration.h
-rw-r--r--src/mongo/util/duration.h50
-rw-r--r--src/mongo/util/duration_test.cpp43
2 files changed, 88 insertions, 5 deletions
diff --git a/src/mongo/util/duration.h b/src/mongo/util/duration.h
index ed187c0ed71..51b2eae14b6 100644
--- a/src/mongo/util/duration.h
+++ b/src/mongo/util/duration.h
@@ -30,10 +30,12 @@
#pragma once
#include <cstdint>
-#include <fmt/format.h>
#include <iosfwd>
#include <limits>
#include <ratio>
+#include <type_traits>
+
+#include <fmt/format.h>
#include "mongo/base/static_assert.h"
#include "mongo/platform/overflow_arithmetic.h"
@@ -60,6 +62,13 @@ using Minutes = Duration<std::ratio<60>>;
using Hours = Duration<std::ratio<3600>>;
using Days = Duration<std::ratio<86400>>;
+namespace duration_detail {
+template <typename>
+inline constexpr bool isMongoDuration = false;
+template <typename... Ts>
+inline constexpr bool isMongoDuration<Duration<Ts...>> = true;
+} // namespace duration_detail
+
//
// Streaming output operators for common duration types. Writes the numerical value followed by
// an abbreviated unit, without a space.
@@ -81,7 +90,9 @@ using HigherPrecisionDuration =
* the ToDuration. For example, Seconds::max() cannot be represented as a Milliseconds, and so
* attempting to cast that value to Milliseconds will throw an exception.
*/
-template <typename ToDuration, typename FromPeriod>
+template <typename ToDuration,
+ typename FromPeriod,
+ std::enable_if_t<duration_detail::isMongoDuration<ToDuration>, int> = 0>
constexpr ToDuration duration_cast(const Duration<FromPeriod>& from) {
using FromOverTo = std::ratio_divide<FromPeriod, typename ToDuration::period>;
if (ToDuration::template isHigherPrecisionThan<Duration<FromPeriod>>()) {
@@ -94,7 +105,10 @@ constexpr ToDuration duration_cast(const Duration<FromPeriod>& from) {
return ToDuration{from.count() / FromOverTo::den};
}
-template <typename ToDuration, typename FromRep, typename FromPeriod>
+template <typename ToDuration,
+ typename FromRep,
+ typename FromPeriod,
+ std::enable_if_t<duration_detail::isMongoDuration<ToDuration>, int> = 0>
constexpr ToDuration duration_cast(const stdx::chrono::duration<FromRep, FromPeriod>& d) {
return duration_cast<ToDuration>(Duration<FromPeriod>{d.count()});
}
@@ -488,4 +502,34 @@ StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Durat
return streamPut(os, dp);
}
+/**
+ * Make a std::chrono::duration from an arithmetic expression and a period ratio.
+ * This does not do any math or precision changes. It's just a type-deduced wrapper
+ * that attaches a period to a number for typesafety. The output std::chrono::duration
+ * will retain the Rep type and value of the input argument.
+ *
+ * E.g:
+ * int waited = 123; // unitless, type-unsafe millisecond count.
+ * auto dur = deduceChronoDuration<std::milli>(waited);
+ * static_assert(std::is_same_v<decltype(dur),
+ * std::chrono::duration<int, std::milli>>);
+ * invariant(dur.count() == 123);
+ *
+ * Note that std::chrono::duration<int, std::milli> is not std::milliseconds,
+ * which has a different (unspecified) Rep type.
+ *
+ * Then mongo::duration_cast can convert the deduced std::chrono::duration to
+ * mongo::Duration, or `std::chrono::duration_cast` be used to adjust the rep
+ * to create a more canonical std::chrono::duration:
+ *
+ * auto durMongo = duration_cast<Milliseconds>(dur);
+ * auto durChrono = duration_cast<std::milliseconds>(dur);
+ *
+ * Order the cast operations carefully to avoid losing range or precision.
+ */
+template <typename Per = std::ratio<1>, typename Rep>
+constexpr auto deduceChronoDuration(const Rep& count) {
+ return stdx::chrono::duration<Rep, Per>{count};
+}
+
} // namespace mongo
diff --git a/src/mongo/util/duration_test.cpp b/src/mongo/util/duration_test.cpp
index a4b70439179..2db2373bcf9 100644
--- a/src/mongo/util/duration_test.cpp
+++ b/src/mongo/util/duration_test.cpp
@@ -29,13 +29,18 @@
#include "mongo/platform/basic.h"
+#include "mongo/util/duration.h"
+
+#include <fmt/format.h>
+
#include "mongo/stdx/chrono.h"
#include "mongo/unittest/unittest.h"
-#include "mongo/util/duration.h"
namespace mongo {
namespace {
+using namespace fmt::literals;
+
// The DurationTestSameType Compare* tests server to check the implementation of the comparison
// operators as well as the compare() method, and so sometimes must explicitly check ASSERT_FALSE(v1
// OP v2). The DurationTestDifferentTypes Compare* tests rely on the fact that the operators all
@@ -155,7 +160,7 @@ TEST(DurationCast, DurationCastConstexpr) {
ASSERT_EQ(2, secs.count());
}
- // Converting from std::chrono::duration to Duration is constexpr.
+ // Converting from stdx::chrono::duration to Duration is constexpr.
{
constexpr auto ms = duration_cast<Milliseconds>(stdx::chrono::seconds(2));
ASSERT_EQ(2000, ms.count());
@@ -247,5 +252,39 @@ TEST(DurationArithmetic, DivideOverflowThrows) {
ASSERT_THROWS_CODE(Milliseconds::min() / -1, AssertionException, ErrorCodes::DurationOverflow);
}
+/** Calls `deduceChronoDuration<Period>(in)`, asserts that it returns `equivalent`. */
+template <typename Period = void, typename In, typename Equivalent>
+auto validateDeduce(In in, Equivalent equivalent) {
+ static constexpr bool useDefaultPeriod = std::is_same_v<Period, void>;
+ using OutPeriod = std::conditional_t<useDefaultPeriod, std::ratio<1>, Period>;
+ auto deduced = [&] {
+ if constexpr (useDefaultPeriod) {
+ return deduceChronoDuration(in);
+ } else {
+ return deduceChronoDuration<Period>(in);
+ }
+ }();
+ ASSERT((std::is_same_v<decltype(deduced), Equivalent>)) << "expected:{}got:{}"_format(
+ demangleName(typeid(Equivalent)), demangleName(typeid(deduced)));
+ ASSERT_EQ(deduced.count(), equivalent.count())
+ << "in:{}, equivalent:{}, deduced:{}"_format(in, equivalent.count(), deduced.count());
+}
+
+TEST(DeduceChronoDuration, DefaultPeriod) {
+ validateDeduce(3600, stdx::chrono::duration<int>(3600));
+ validateDeduce(3600, stdx::chrono::duration<int>(3600));
+ validateDeduce((short)3600, stdx::chrono::duration<short>(3600));
+ validateDeduce(3600u, stdx::chrono::duration<unsigned>(3600));
+ validateDeduce(3600., stdx::chrono::duration<double>(3600));
+ validateDeduce(3600.5, stdx::chrono::duration<double>(3600.5));
+ validateDeduce(-3600.5, stdx::chrono::duration<double>(-3600.5));
+}
+
+TEST(DeduceChronoDuration, ExplicitPeriod) {
+ validateDeduce<std::milli>(123, stdx::chrono::duration<int, std::milli>(123));
+ validateDeduce<std::nano>(123, stdx::chrono::duration<int, std::nano>(123));
+ validateDeduce<std::ratio<45, 123>>(50, stdx::chrono::duration<int, std::ratio<45, 123>>(50));
+}
+
} // namespace
} // namespace mongo