No need for a wrapping structure for posixTimeToDuration
authorYedidya Feldblum <yfeldblum@fb.com>
Mon, 20 Nov 2017 21:29:48 +0000 (13:29 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Mon, 20 Nov 2017 21:47:32 +0000 (13:47 -0800)
Summary:
[Folly] No need for a wrapping structure for `posixTimeToDuration`.

We can just use a variant of tag dispatch. In this variant, we pass to `posixTimeToDuration` a default-initialized value of the desired return type and we write overload templates for each possible variant. The argument is used purely for overload resolution and return type deduction, not for its runtime value. It is slightly different from tag dispatch because we do not use separate types which are purely tag types.

Reviewed By: simpkins

Differential Revision: D6371572

fbshipit-source-id: 1987dee31fceec8733caa61495e96489dbf1ca39

folly/chrono/Conv.h

index 95aff32ac47edd9c83f10897279f112129bbb8ad..688658ec37a60a5e28530ae10d0e8a065927bcb9 100644 (file)
@@ -323,8 +323,8 @@ struct CheckOverflowToDuration<true> {
 };
 
 /**
 };
 
 /**
- * Helper class to convert a POSIX-style pair of (seconds, subseconds)
- * to a std::chrono::duration type.
+ * Convert a timeval or a timespec to a std::chrono::duration with second
+ * granularity.
  *
  * The SubsecondRatio template parameter specifies what type of subseconds to
  * return.  This must have a numerator of 1.
  *
  * The SubsecondRatio template parameter specifies what type of subseconds to
  * return.  This must have a numerator of 1.
@@ -332,155 +332,156 @@ struct CheckOverflowToDuration<true> {
  * The input must be in normalized form: the subseconds field must be greater
  * than or equal to 0, and less than SubsecondRatio::den (i.e., less than 1
  * second).
  * The input must be in normalized form: the subseconds field must be greater
  * than or equal to 0, and less than SubsecondRatio::den (i.e., less than 1
  * second).
- *
- * This default implementation is only used for unusual std::chrono::duration
- * types where neither the numerator nor denominator are 1.
- */
-template <typename Tgt>
-struct PosixTimeToDuration {
-  template <typename SubsecondRatio, typename Seconds, typename Subseconds>
-  static Expected<Tgt, ConversionCode> cast(
-      Seconds seconds,
-      Subseconds subseconds);
-};
-
-/**
- * Convert a timeval or a timespec to a std::chrono::duration with second
- * granularity.
  */
  */
-template <typename Rep>
-struct PosixTimeToDuration<std::chrono::duration<Rep, std::ratio<1, 1>>> {
-  using Tgt = std::chrono::duration<Rep, std::ratio<1, 1>>;
-
-  template <typename SubsecondRatio, typename Seconds, typename Subseconds>
-  static Expected<Tgt, ConversionCode> cast(
-      Seconds seconds,
-      Subseconds subseconds) {
-    static_assert(Tgt::period::num == 1, "special case expecting num==1");
-    static_assert(Tgt::period::den == 1, "special case expecting den==1");
-    static_assert(
-        SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<1, 1>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(Tgt::period::num == 1, "special case expecting num==1");
+  static_assert(Tgt::period::den == 1, "special case expecting den==1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
 
 
-    auto outputSeconds = tryTo<typename Tgt::rep>(seconds);
-    if (outputSeconds.hasError()) {
-      return makeUnexpected(outputSeconds.error());
-    }
+  auto outputSeconds = tryTo<typename Tgt::rep>(seconds);
+  if (outputSeconds.hasError()) {
+    return makeUnexpected(outputSeconds.error());
+  }
 
 
-    if (std::is_floating_point<typename Tgt::rep>::value) {
-      return Tgt{typename Tgt::rep(seconds) +
-                 (typename Tgt::rep(subseconds) / SubsecondRatio::den)};
-    }
+  if (std::is_floating_point<typename Tgt::rep>::value) {
+    return Tgt{typename Tgt::rep(seconds) +
+               (typename Tgt::rep(subseconds) / SubsecondRatio::den)};
+  }
 
 
-    // If the value is negative, we have to round up a non-zero subseconds value
-    if (UNLIKELY(outputSeconds.value() < 0) && subseconds > 0) {
-      if (UNLIKELY(
-              outputSeconds.value() ==
-              std::numeric_limits<typename Tgt::rep>::lowest())) {
-        return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
-      }
-      return Tgt{outputSeconds.value() + 1};
+  // If the value is negative, we have to round up a non-zero subseconds value
+  if (UNLIKELY(outputSeconds.value() < 0) && subseconds > 0) {
+    if (UNLIKELY(
+            outputSeconds.value() ==
+            std::numeric_limits<typename Tgt::rep>::lowest())) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
     }
     }
-
-    return Tgt{outputSeconds.value()};
+    return Tgt{outputSeconds.value() + 1};
   }
   }
-};
+
+  return Tgt{outputSeconds.value()};
+}
 
 /**
  * Convert a timeval or a timespec to a std::chrono::duration with subsecond
  * granularity
  */
 
 /**
  * Convert a timeval or a timespec to a std::chrono::duration with subsecond
  * granularity
  */
-template <typename Rep, std::intmax_t Denominator>
-struct PosixTimeToDuration<
-    std::chrono::duration<Rep, std::ratio<1, Denominator>>> {
-  using Tgt = std::chrono::duration<Rep, std::ratio<1, Denominator>>;
-
-  template <typename SubsecondRatio, typename Seconds, typename Subseconds>
-  static Expected<Tgt, ConversionCode> cast(
-      Seconds seconds,
-      Subseconds subseconds) {
-    static_assert(Tgt::period::num == 1, "special case expecting num==1");
-    static_assert(Tgt::period::den != 1, "special case expecting den!=1");
-    static_assert(
-        SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep,
+    std::intmax_t Denominator>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<1, Denominator>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(Tgt::period::num == 1, "special case expecting num==1");
+  static_assert(Tgt::period::den != 1, "special case expecting den!=1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
 
 
-    auto errorCode = detail::CheckOverflowToDuration<
-        std::is_floating_point<typename Tgt::rep>::value>::
-        template check<Tgt, SubsecondRatio>(seconds, subseconds);
-    if (errorCode != ConversionCode::SUCCESS) {
-      return makeUnexpected(errorCode);
-    }
+  auto errorCode = detail::CheckOverflowToDuration<
+      std::is_floating_point<typename Tgt::rep>::value>::
+      template check<Tgt, SubsecondRatio>(seconds, subseconds);
+  if (errorCode != ConversionCode::SUCCESS) {
+    return makeUnexpected(errorCode);
+  }
 
 
-    if (LIKELY(seconds >= 0)) {
-      return std::chrono::duration_cast<Tgt>(
-                 std::chrono::duration<typename Tgt::rep>{seconds}) +
-          std::chrono::duration_cast<Tgt>(
-                 std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
-                     subseconds});
-    } else {
-      // For negative numbers we have to round subseconds up towards zero, even
-      // though it is a positive value, since the overall value is negative.
-      return std::chrono::duration_cast<Tgt>(
-                 std::chrono::duration<typename Tgt::rep>{seconds + 1}) -
-          std::chrono::duration_cast<Tgt>(
-                 std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
-                     SubsecondRatio::den - subseconds});
-    }
+  if (LIKELY(seconds >= 0)) {
+    return std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep>{seconds}) +
+        std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
+                   subseconds});
+  } else {
+    // For negative numbers we have to round subseconds up towards zero, even
+    // though it is a positive value, since the overall value is negative.
+    return std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep>{seconds + 1}) -
+        std::chrono::duration_cast<Tgt>(
+               std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
+                   SubsecondRatio::den - subseconds});
   }
   }
-};
+}
 
 /**
  * Convert a timeval or a timespec to a std::chrono::duration with
  * granularity coarser than 1 second.
  */
 
 /**
  * Convert a timeval or a timespec to a std::chrono::duration with
  * granularity coarser than 1 second.
  */
-template <typename Rep, std::intmax_t Numerator>
-struct PosixTimeToDuration<
-    std::chrono::duration<Rep, std::ratio<Numerator, 1>>> {
-  using Tgt = std::chrono::duration<Rep, std::ratio<Numerator, 1>>;
-
-  template <typename SubsecondRatio, typename Seconds, typename Subseconds>
-  static Expected<Tgt, ConversionCode> cast(
-      Seconds seconds,
-      Subseconds subseconds) {
-    static_assert(Tgt::period::num != 1, "special case expecting num!=1");
-    static_assert(Tgt::period::den == 1, "special case expecting den==1");
-    static_assert(
-        SubsecondRatio::num == 1, "subsecond numerator should always be 1");
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep,
+    std::intmax_t Numerator>
+auto posixTimeToDuration(
+    Seconds seconds,
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<Numerator, 1>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
+  static_assert(Tgt::period::num != 1, "special case expecting num!=1");
+  static_assert(Tgt::period::den == 1, "special case expecting den==1");
+  static_assert(
+      SubsecondRatio::num == 1, "subsecond numerator should always be 1");
 
 
-    if (UNLIKELY(seconds < 0) && subseconds > 0) {
-      // Increment seconds by one to handle truncation of negative numbers
-      // properly.
-      if (UNLIKELY(seconds == std::numeric_limits<Seconds>::lowest())) {
-        return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
-      }
-      seconds += 1;
+  if (UNLIKELY(seconds < 0) && subseconds > 0) {
+    // Increment seconds by one to handle truncation of negative numbers
+    // properly.
+    if (UNLIKELY(seconds == std::numeric_limits<Seconds>::lowest())) {
+      return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
     }
     }
+    seconds += 1;
+  }
 
 
-    if (std::is_floating_point<typename Tgt::rep>::value) {
-      // Convert to the floating point type before performing the division
-      return Tgt{static_cast<typename Tgt::rep>(seconds) / Tgt::period::num};
-    } else {
-      // Perform the division as an integer, and check that the result fits in
-      // the output integer type
-      auto outputValue = (seconds / Tgt::period::num);
-      auto expectedOuput = tryTo<typename Tgt::rep>(outputValue);
-      if (expectedOuput.hasError()) {
-        return makeUnexpected(expectedOuput.error());
-      }
-
-      return Tgt{expectedOuput.value()};
+  if (std::is_floating_point<typename Tgt::rep>::value) {
+    // Convert to the floating point type before performing the division
+    return Tgt{static_cast<typename Tgt::rep>(seconds) / Tgt::period::num};
+  } else {
+    // Perform the division as an integer, and check that the result fits in
+    // the output integer type
+    auto outputValue = (seconds / Tgt::period::num);
+    auto expectedOuput = tryTo<typename Tgt::rep>(outputValue);
+    if (expectedOuput.hasError()) {
+      return makeUnexpected(expectedOuput.error());
     }
     }
+
+    return Tgt{expectedOuput.value()};
   }
   }
-};
+}
 
 /**
 
 /**
- * PosixTimeToDuration::cast() implementation for the default case
- * with unusual durations where neither the numerator nor denominator are 1.
+ * Convert a timeval or timespec to a std::chrono::duration
+ *
+ * This overload is only used for unusual durations where neither the numerator
+ * nor denominator are 1.
  */
  */
-template <typename Tgt>
-template <typename SubsecondRatio, typename Seconds, typename Subseconds>
-Expected<Tgt, ConversionCode> PosixTimeToDuration<Tgt>::cast(
+template <
+    typename SubsecondRatio,
+    typename Seconds,
+    typename Subseconds,
+    typename Rep,
+    std::intmax_t Denominator,
+    std::intmax_t Numerator>
+auto posixTimeToDuration(
     Seconds seconds,
     Seconds seconds,
-    Subseconds subseconds) {
+    Subseconds subseconds,
+    std::chrono::duration<Rep, std::ratio<Numerator, Denominator>> dummy)
+    -> Expected<decltype(dummy), ConversionCode> {
+  using Tgt = decltype(dummy);
   static_assert(
       Tgt::period::num != 1, "should use special-case code when num==1");
   static_assert(
   static_assert(
       Tgt::period::num != 1, "should use special-case code when num==1");
   static_assert(
@@ -534,8 +535,7 @@ Expected<Tgt, ConversionCode> tryPosixTimeToDuration(
     subseconds = remainder;
   }
 
     subseconds = remainder;
   }
 
-  using Converter = PosixTimeToDuration<Tgt>;
-  return Converter::template cast<SubsecondRatio>(seconds, subseconds);
+  return posixTimeToDuration<SubsecondRatio>(seconds, subseconds, Tgt{});
 }
 
 } // namespace detail
 }
 
 } // namespace detail