Commit 3c9a6e9a authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

No need for a wrapping structure for posixTimeToDuration

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