X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2FConv.h;h=72518d808f5ad895ad04c33eadb5a87a711f5cc9;hp=de369c0a29c3cb0d10b5d980a0650ee6729095ec;hb=7b116ffe3b116ffc3c2089ca0b864ca7ebb1d28c;hpb=4f0bc5272f90bfcb7a18e6a1a4359a202a1b8cf9 diff --git a/folly/Conv.h b/folly/Conv.h index de369c0a..72518d80 100644 --- a/folly/Conv.h +++ b/folly/Conv.h @@ -1,5 +1,5 @@ /* - * Copyright 2012 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,75 +21,165 @@ * @author Andrei Alexandrescu (andrei.alexandrescu@fb.com) */ -#ifndef FOLLY_BASE_CONV_H_ -#define FOLLY_BASE_CONV_H_ +#pragma once -#include "folly/FBString.h" -#include "folly/Likely.h" -#include "folly/Preprocessor.h" -#include "folly/Range.h" - -#include -#include +#include +#include +#include +#include #include +#include #include #include -#include +#include #include +#include -#include "double-conversion.h" // V8 JavaScript implementation +#include +#include // V8 JavaScript implementation -#define FOLLY_RANGE_CHECK(condition, message) \ - ((condition) ? (void)0 : throw std::range_error( \ - (__FILE__ "(" + std::to_string((long long int) __LINE__) + "): " \ - + (message)).c_str())) +#include +#include +#include +#include +#include +#include +#include +#include namespace folly { +// Keep this in sync with kErrorStrings in Conv.cpp +enum class ConversionCode : unsigned char { + SUCCESS, + EMPTY_INPUT_STRING, + NO_DIGITS, + BOOL_OVERFLOW, + BOOL_INVALID_VALUE, + NON_DIGIT_CHAR, + INVALID_LEADING_CHAR, + POSITIVE_OVERFLOW, + NEGATIVE_OVERFLOW, + STRING_TO_FLOAT_ERROR, + NON_WHITESPACE_AFTER_END, + ARITH_POSITIVE_OVERFLOW, + ARITH_NEGATIVE_OVERFLOW, + ARITH_LOSS_OF_PRECISION, + NUM_ERROR_CODES, // has to be the last entry +}; + +struct ConversionErrorBase : std::range_error { + using std::range_error::range_error; +}; + +class ConversionError : public ConversionErrorBase { + public: + ConversionError(const std::string& str, ConversionCode code) + : ConversionErrorBase(str), code_(code) {} + + ConversionError(const char* str, ConversionCode code) + : ConversionErrorBase(str), code_(code) {} + + ConversionCode errorCode() const { + return code_; + } + + private: + ConversionCode code_; +}; + /******************************************************************************* - * Integral to integral + * Custom Error Translation + * + * Your overloaded parseTo() function can return a custom error code on failure. + * ::folly::to() will call makeConversionError to translate that error code into + * an object to throw. makeConversionError is found by argument-dependent + * lookup. It should have this signature: + * + * namespace other_namespace { + * enum YourErrorCode { BAD_ERROR, WORSE_ERROR }; + * + * struct YourConversionError : ConversionErrorBase { + * YourConversionError(const char* what) : ConversionErrorBase(what) {} + * }; + * + * YourConversionError + * makeConversionError(YourErrorCode code, ::folly::StringPiece sp) { + * ... + * return YourConversionError(messageString); + * } ******************************************************************************/ +ConversionError makeConversionError(ConversionCode code, StringPiece sp); +namespace detail { /** - * Checked conversion from integral to integral. The checks are only - * performed when meaningful, e.g. conversion from int to long goes - * unchecked. + * Enforce that the suffix following a number is made up only of whitespace. */ -template -typename std::enable_if< - std::is_integral::value && std::is_integral::value, - Tgt>::type -to(const Src & value) { - /* static */ if (std::numeric_limits::max() - < std::numeric_limits::max()) { - FOLLY_RANGE_CHECK(value <= std::numeric_limits::max(), - "Overflow"); +inline ConversionCode enforceWhitespaceErr(StringPiece sp) { + for (auto c : sp) { + if (UNLIKELY(!std::isspace(c))) { + return ConversionCode::NON_WHITESPACE_AFTER_END; + } } - /* static */ if (std::is_signed::value && - (!std::is_signed::value || sizeof(Src) > sizeof(Tgt))) { - FOLLY_RANGE_CHECK(value >= std::numeric_limits::min(), - "Negative overflow"); + return ConversionCode::SUCCESS; +} + +/** + * Keep this implementation around for prettyToDouble(). + */ +inline void enforceWhitespace(StringPiece sp) { + auto err = enforceWhitespaceErr(sp); + if (err != ConversionCode::SUCCESS) { + throw makeConversionError(err, sp); } - return static_cast(value); +} +} + +/** + * The identity conversion function. + * tryTo(T) returns itself for all types T. + */ +template +typename std::enable_if< + std::is_same::type>::value, + Expected>::type +tryTo(Src&& value) { + return std::forward(value); +} + +template +typename std::enable_if< + std::is_same::type>::value, + Tgt>::type +to(Src&& value) { + return std::forward(value); } /******************************************************************************* - * Floating point to floating point + * Arithmetic to boolean ******************************************************************************/ +/** + * Unchecked conversion from arithmetic to boolean. This is different from the + * other arithmetic conversions because we use the C convention of treating any + * non-zero value as true, instead of range checking. + */ template typename std::enable_if< - std::is_floating_point::value && std::is_floating_point::value, - Tgt>::type -to(const Src & value) { - /* static */ if (std::numeric_limits::max() < - std::numeric_limits::max()) { - FOLLY_RANGE_CHECK(value <= std::numeric_limits::max(), - "Overflow"); - FOLLY_RANGE_CHECK(value >= -std::numeric_limits::max(), - "Negative overflow"); - } - return boost::implicit_cast(value); + std::is_arithmetic::value && !std::is_same::value && + std::is_same::value, + Expected>::type +tryTo(const Src& value) { + return value != Src(); +} + +template +typename std::enable_if< + std::is_arithmetic::value && !std::is_same::value && + std::is_same::value, + Tgt>::type +to(const Src& value) { + return value != Src(); } /******************************************************************************* @@ -98,30 +188,104 @@ to(const Src & value) { namespace detail { -template struct IsSomeString { - enum { value = std::is_same::value - || std::is_same::value }; +#ifdef _MSC_VER +// MSVC can't quite figure out the LastElementImpl::call() stuff +// in the base implementation, so we have to use tuples instead, +// which result in significantly more templates being compiled, +// though the runtime performance is the same. + +template +auto getLastElement(Ts&&... ts) -> decltype( + std::get(std::forward_as_tuple(std::forward(ts)...))) { + return std::get( + std::forward_as_tuple(std::forward(ts)...)); +} + +inline void getLastElement() {} + +template +struct LastElementType : std::tuple_element> {}; + +template <> +struct LastElementType<0> { + using type = void; }; -template -const T& getLastElement(const T & v) { - return v; -} +template +struct LastElement + : std::decay::type> {}; +#else +template +struct LastElementImpl { + static void call(Ignored...) {} +}; -template -typename std::tuple_element< - sizeof...(Ts), - std::tuple >::type const& - getLastElement(const T& v, const Ts&... vs) { - return getLastElement(vs...); +template +struct LastElementImpl { + template + static Last call(Ignored..., Last&& last) { + return std::forward(last); + } +}; + +template +auto getLastElement(const Ts&... ts) + -> decltype(LastElementImpl::call(ts...)) { + return LastElementImpl::call(ts...); } +template +struct LastElement : std::decay::call(std::declval()...))> { +}; +#endif + } // namespace detail /******************************************************************************* * Conversions from integral types to string types. ******************************************************************************/ +#if FOLLY_HAVE_INT128_T +namespace detail { + +template +constexpr unsigned int +digitsEnough() { + return (unsigned int)(ceil(sizeof(IntegerType) * CHAR_BIT * M_LN2 / M_LN10)); +} + +inline size_t +unsafeTelescope128(char * buffer, size_t room, unsigned __int128 x) { + typedef unsigned __int128 Usrc; + size_t p = room - 1; + + while (x >= (Usrc(1) << 64)) { // Using 128-bit division while needed + const auto y = x / 10; + const auto digit = x % 10; + + buffer[p--] = '0' + digit; + x = y; + } + + uint64_t xx = x; // Moving to faster 64-bit division thereafter + + while (xx >= 10) { + const auto y = xx / 10ULL; + const auto digit = xx % 10ULL; + + buffer[p--] = '0' + digit; + xx = y; + } + + buffer[p] = '0' + xx; + + return p; +} + +} +#endif + /** * Returns the number of digits in the base 10 representation of an * uint64_t. Useful for preallocating buffers and such. It's also used @@ -130,6 +294,58 @@ typename std::tuple_element< */ inline uint32_t digits10(uint64_t v) { +#ifdef __x86_64__ + + // For this arch we can get a little help from specialized CPU instructions + // which can count leading zeroes; 64 minus that is appx. log (base 2). + // Use that to approximate base-10 digits (log_10) and then adjust if needed. + + // 10^i, defined for i 0 through 19. + // This is 20 * 8 == 160 bytes, which fits neatly into 5 cache lines + // (assuming a cache line size of 64). + static const uint64_t powersOf10[20] FOLLY_ALIGNED(64) = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000UL, + }; + + // "count leading zeroes" operation not valid; for 0; special case this. + if UNLIKELY (! v) { + return 1; + } + + // bits is in the ballpark of log_2(v). + const uint8_t leadingZeroes = __builtin_clzll(v); + const auto bits = 63 - leadingZeroes; + + // approximate log_10(v) == log_10(2) * bits. + // Integer magic below: 77/256 is appx. 0.3010 (log_10(2)). + // The +1 is to make this the ceiling of the log_10 estimate. + const uint32_t minLength = 1 + ((bits * 77) >> 8); + + // return that log_10 lower bound, plus adjust if input >= 10^(that bound) + // in case there's a small error and we misjudged length. + return minLength + (uint32_t) (UNLIKELY (v >= powersOf10[minLength])); + +#else + uint32_t result = 1; for (;;) { if (LIKELY(v < 10)) return result; @@ -140,6 +356,8 @@ inline uint32_t digits10(uint64_t v) { v /= 10000U; result += 4; } + +#endif } /** @@ -165,12 +383,12 @@ inline uint32_t uint64ToBufferUnsafe(uint64_t v, char *const buffer) { // Keep these together so a peephole optimization "sees" them and // computes them in one shot. auto const q = v / 10; - auto const r = static_cast(v % 10); + auto const r = static_cast(v % 10); buffer[pos--] = '0' + r; v = q; } // Last digit is trivial to handle - buffer[pos] = static_cast(v) + '0'; + buffer[pos] = static_cast(v) + '0'; return result; } @@ -182,13 +400,21 @@ void toAppend(char value, Tgt * result) { *result += value; } +template +constexpr typename std::enable_if< + std::is_same::value, + size_t>::type +estimateSpaceNeeded(T) { + return 1; +} + /** * Everything implicitly convertible to const char* gets appended. */ template typename std::enable_if< std::is_convertible::value - && detail::IsSomeString::value>::type + && IsSomeString::value>::type toAppend(Src value, Tgt * result) { // Treat null pointers like an empty string, as in: // operator<<(std::ostream&, const char*). @@ -198,12 +424,47 @@ toAppend(Src value, Tgt * result) { } } +template +typename std::enable_if::value, size_t>:: + type + estimateSpaceNeeded(Src value) { + const char *c = value; + if (c) { + return folly::StringPiece(value).size(); + }; + return 0; +} + +template +typename std::enable_if< + (std::is_convertible::value || + IsSomeString::value) && + !std::is_convertible::value, + size_t>::type +estimateSpaceNeeded(Src value) { + return folly::StringPiece(value).size(); +} + +template <> +inline size_t estimateSpaceNeeded(std::nullptr_t /* value */) { + return 0; +} + +template +typename std::enable_if< + std::is_pointer::value && + IsSomeString>::value, + size_t>::type +estimateSpaceNeeded(Src value) { + return value->size(); +} + /** * Strings get appended, too. */ template typename std::enable_if< - detail::IsSomeString::value && detail::IsSomeString::value>::type + IsSomeString::value && IsSomeString::value>::type toAppend(const Src& value, Tgt * result) { result->append(value); } @@ -213,7 +474,7 @@ toAppend(const Src& value, Tgt * result) { */ template typename std::enable_if< - detail::IsSomeString::value>::type + IsSomeString::value>::type toAppend(StringPiece value, Tgt * result) { result->append(value.data(), value.size()); } @@ -224,33 +485,100 @@ toAppend(StringPiece value, Tgt * result) { */ template typename std::enable_if< - detail::IsSomeString::value>::type + IsSomeString::value>::type toAppend(const fbstring& value, Tgt * result) { result->append(value.data(), value.size()); } +#if FOLLY_HAVE_INT128_T +/** + * Special handling for 128 bit integers. + */ + +template +void +toAppend(__int128 value, Tgt * result) { + typedef unsigned __int128 Usrc; + char buffer[detail::digitsEnough() + 1]; + size_t p; + + if (value < 0) { + p = detail::unsafeTelescope128(buffer, sizeof(buffer), -Usrc(value)); + buffer[--p] = '-'; + } else { + p = detail::unsafeTelescope128(buffer, sizeof(buffer), value); + } + + result->append(buffer + p, buffer + sizeof(buffer)); +} + +template +void +toAppend(unsigned __int128 value, Tgt * result) { + char buffer[detail::digitsEnough()]; + size_t p; + + p = detail::unsafeTelescope128(buffer, sizeof(buffer), value); + + result->append(buffer + p, buffer + sizeof(buffer)); +} + +template +constexpr typename std::enable_if< + std::is_same::value, + size_t>::type +estimateSpaceNeeded(T) { + return detail::digitsEnough<__int128>(); +} + +template +constexpr typename std::enable_if< + std::is_same::value, + size_t>::type +estimateSpaceNeeded(T) { + return detail::digitsEnough(); +} + +#endif + /** * int32_t and int64_t to string (by appending) go through here. The * result is APPENDED to a preexisting string passed as the second - * parameter. For convenience, the function also returns a reference - * to *result. This should be efficient with fbstring because fbstring + * parameter. This should be efficient with fbstring because fbstring * incurs no dynamic allocation below 23 bytes and no number has more * than 22 bytes in its textual representation (20 for digits, one for * sign, one for the terminating 0). */ template typename std::enable_if< - std::is_integral::value && std::is_signed::value - && detail::IsSomeString::value && sizeof(Src) >= 4>::type + std::is_integral::value && std::is_signed::value && + IsSomeString::value && sizeof(Src) >= 4>::type toAppend(Src value, Tgt * result) { - typedef typename std::make_unsigned::type Usrc; char buffer[20]; if (value < 0) { result->push_back('-'); - result->append(buffer, uint64ToBufferUnsafe(-uint64_t(value), buffer)); + result->append( + buffer, + uint64ToBufferUnsafe(~static_cast(value) + 1, buffer)); } else { - result->append(buffer, uint64ToBufferUnsafe(value, buffer)); + result->append(buffer, uint64ToBufferUnsafe(uint64_t(value), buffer)); + } +} + +template +typename std::enable_if< + std::is_integral::value && std::is_signed::value + && sizeof(Src) >= 4 && sizeof(Src) < 16, + size_t>::type +estimateSpaceNeeded(Src value) { + if (value < 0) { + // When "value" is the smallest negative, negating it would evoke + // undefined behavior, so, instead of writing "-value" below, we write + // "~static_cast(value) + 1" + return 1 + digits10(~static_cast(value) + 1); } + + return digits10(static_cast(value)); } /** @@ -259,10 +587,19 @@ toAppend(Src value, Tgt * result) { template typename std::enable_if< std::is_integral::value && !std::is_signed::value - && detail::IsSomeString::value && sizeof(Src) >= 4>::type + && IsSomeString::value && sizeof(Src) >= 4>::type toAppend(Src value, Tgt * result) { char buffer[20]; - result->append(buffer, buffer + uint64ToBufferUnsafe(value, buffer)); + result->append(buffer, uint64ToBufferUnsafe(value, buffer)); +} + +template +typename std::enable_if< + std::is_integral::value && !std::is_signed::value + && sizeof(Src) >= 4 && sizeof(Src) < 16, + size_t>::type +estimateSpaceNeeded(Src value) { + return digits10(value); } /** @@ -272,7 +609,7 @@ toAppend(Src value, Tgt * result) { template typename std::enable_if< std::is_integral::value - && detail::IsSomeString::value && sizeof(Src) < 4>::type + && IsSomeString::value && sizeof(Src) < 4>::type toAppend(Src value, Tgt * result) { typedef typename std::conditional::value, int64_t, uint64_t>::type @@ -280,55 +617,52 @@ toAppend(Src value, Tgt * result) { toAppend(static_cast(value), result); } -#if defined(__GNUC__) && __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) -// std::underlying_type became available by gcc 4.7.0 +template +typename std::enable_if< + std::is_integral::value + && sizeof(Src) < 4 + && !std::is_same::value, + size_t>::type +estimateSpaceNeeded(Src value) { + typedef typename + std::conditional::value, int64_t, uint64_t>::type + Intermediate; + return estimateSpaceNeeded(static_cast(value)); +} /** * Enumerated values get appended as integers. */ template typename std::enable_if< - std::is_enum::value && detail::IsSomeString::value>::type + std::is_enum::value && IsSomeString::value>::type toAppend(Src value, Tgt * result) { toAppend( static_cast::type>(value), result); } -#else - -/** - * Enumerated values get appended as integers. - */ -template +template typename std::enable_if< - std::is_enum::value && detail::IsSomeString::value>::type -toAppend(Src value, Tgt * result) { - /* static */ if (Src(-1) < 0) { - /* static */ if (sizeof(Src) <= sizeof(int)) { - toAppend(static_cast(value), result); - } else { - toAppend(static_cast(value), result); - } - } else { - /* static */ if (sizeof(Src) <= sizeof(int)) { - toAppend(static_cast(value), result); - } else { - toAppend(static_cast(value), result); - } - } + std::is_enum::value, size_t>::type +estimateSpaceNeeded(Src value) { + return estimateSpaceNeeded( + static_cast::type>(value)); } -#endif // gcc 4.7 onwards - /******************************************************************************* * Conversions from floating-point types to string types. ******************************************************************************/ +namespace detail { +constexpr int kConvMaxDecimalInShortestLow = -6; +constexpr int kConvMaxDecimalInShortestHigh = 21; +} // folly::detail + /** Wrapper around DoubleToStringConverter **/ template typename std::enable_if< std::is_floating_point::value - && detail::IsSomeString::value>::type + && IsSomeString::value>::type toAppend( Src value, Tgt * result, @@ -337,9 +671,9 @@ toAppend( using namespace double_conversion; DoubleToStringConverter conv(DoubleToStringConverter::NO_FLAGS, - "infinity", "NaN", 'E', - -6, // decimal in shortest low - 21, // decimal in shortest high + "Infinity", "NaN", 'E', + detail::kConvMaxDecimalInShortestLow, + detail::kConvMaxDecimalInShortestHigh, 6, // max leading padding zeros 1); // max trailing padding zeros char buffer[256]; @@ -349,14 +683,14 @@ toAppend( conv.ToShortest(value, &builder); break; case DoubleToStringConverter::FIXED: - conv.ToFixed(value, numDigits, &builder); + conv.ToFixed(value, int(numDigits), &builder); break; default: CHECK(mode == DoubleToStringConverter::PRECISION); - conv.ToPrecision(value, numDigits, &builder); + conv.ToPrecision(value, int(numDigits), &builder); break; } - const size_t length = builder.position(); + const size_t length = size_t(builder.position()); builder.Finalize(); result->append(buffer, length); } @@ -367,631 +701,861 @@ toAppend( template typename std::enable_if< std::is_floating_point::value - && detail::IsSomeString::value>::type + && IsSomeString::value>::type toAppend(Src value, Tgt * result) { toAppend( value, result, double_conversion::DoubleToStringConverter::SHORTEST, 0); } /** - * Variadic conversion to string. Appends each element in turn. + * Upper bound of the length of the output from + * DoubleToStringConverter::ToShortest(double, StringBuilder*), + * as used in toAppend(double, string*). + */ +template +typename std::enable_if< + std::is_floating_point::value, size_t>::type +estimateSpaceNeeded(Src value) { + // kBase10MaximalLength is 17. We add 1 for decimal point, + // e.g. 10.0/9 is 17 digits and 18 characters, including the decimal point. + constexpr int kMaxMantissaSpace = + double_conversion::DoubleToStringConverter::kBase10MaximalLength + 1; + // strlen("E-") + digits10(numeric_limits::max_exponent10) + constexpr int kMaxExponentSpace = 2 + 3; + static const int kMaxPositiveSpace = std::max({ + // E.g. 1.1111111111111111E-100. + kMaxMantissaSpace + kMaxExponentSpace, + // E.g. 0.000001.1111111111111111, if kConvMaxDecimalInShortestLow is -6. + kMaxMantissaSpace - detail::kConvMaxDecimalInShortestLow, + // If kConvMaxDecimalInShortestHigh is 21, then 1e21 is the smallest + // number > 1 which ToShortest outputs in exponential notation, + // so 21 is the longest non-exponential number > 1. + detail::kConvMaxDecimalInShortestHigh + }); + return size_t( + kMaxPositiveSpace + + (value < 0 ? 1 : 0)); // +1 for minus sign, if negative +} + +/** + * This can be specialized, together with adding specialization + * for estimateSpaceNeed for your type, so that we allocate + * as much as you need instead of the default + */ +template +struct HasLengthEstimator : std::false_type {}; + +template +constexpr typename std::enable_if< + !std::is_fundamental::value +#if FOLLY_HAVE_INT128_T + // On OSX 10.10, is_fundamental<__int128> is false :-O + && !std::is_same<__int128, Src>::value + && !std::is_same::value +#endif + && !IsSomeString::value + && !std::is_convertible::value + && !std::is_convertible::value + && !std::is_enum::value + && !HasLengthEstimator::value, + size_t>::type +estimateSpaceNeeded(const Src&) { + return sizeof(Src) + 1; // dumbest best effort ever? +} + +namespace detail { + +template +typename std::enable_if::value, size_t>::type +estimateSpaceToReserve(size_t sofar, Tgt*) { + return sofar; +} + +template +size_t estimateSpaceToReserve(size_t sofar, const T& v, const Ts&... vs) { + return estimateSpaceToReserve(sofar + estimateSpaceNeeded(v), vs...); +} + +template +void reserveInTarget(const Ts&...vs) { + getLastElement(vs...)->reserve(estimateSpaceToReserve(0, vs...)); +} + +template +void reserveInTargetDelim(const Delimiter& d, const Ts&...vs) { + static_assert(sizeof...(vs) >= 2, "Needs at least 2 args"); + size_t fordelim = (sizeof...(vs) - 2) * + estimateSpaceToReserve(0, d, static_cast(nullptr)); + getLastElement(vs...)->reserve(estimateSpaceToReserve(fordelim, vs...)); +} + +/** + * Variadic base case: append one element */ +template +typename std::enable_if< + IsSomeString::type> + ::value>::type +toAppendStrImpl(const T& v, Tgt result) { + toAppend(v, result); +} + template -typename std::enable_if= 2 - && detail::IsSomeString< - typename std::remove_pointer< - typename std::tuple_element< - sizeof...(Ts) - 1, std::tuple - >::type>::type>::value>::type -toAppend(const T& v, const Ts&... vs) { +typename std::enable_if< + sizeof...(Ts) >= 2 && + IsSomeString::type>::type>::value>::type +toAppendStrImpl(const T& v, const Ts&... vs) { + toAppend(v, getLastElement(vs...)); + toAppendStrImpl(vs...); +} + +template +typename std::enable_if< + IsSomeString::type>::value>::type +toAppendDelimStrImpl(const Delimiter& /* delim */, const T& v, Tgt result) { + toAppend(v, result); +} + +template +typename std::enable_if< + sizeof...(Ts) >= 2 && + IsSomeString::type>::type>::value>::type +toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) { + // we are really careful here, calling toAppend with just one element does + // not try to estimate space needed (as we already did that). If we call + // toAppend(v, delim, ....) we would do unnecesary size calculation toAppend(v, detail::getLastElement(vs...)); + toAppend(delim, detail::getLastElement(vs...)); + toAppendDelimStrImpl(delim, vs...); +} +} // folly::detail + + +/** + * Variadic conversion to string. Appends each element in turn. + * If we have two or more things to append, we it will not reserve + * the space for them and will depend on strings exponential growth. + * If you just append once consider using toAppendFit which reserves + * the space needed (but does not have exponential as a result). + * + * Custom implementations of toAppend() can be provided in the same namespace as + * the type to customize printing. estimateSpaceNeed() may also be provided to + * avoid reallocations in toAppendFit(): + * + * namespace other_namespace { + * + * template + * void toAppend(const OtherType&, String* out); + * + * // optional + * size_t estimateSpaceNeeded(const OtherType&); + * + * } + */ +template +typename std::enable_if< + sizeof...(Ts) >= 3 && + IsSomeString::type>::type>::value>::type +toAppend(const Ts&... vs) { + ::folly::detail::toAppendStrImpl(vs...); +} + +#ifdef _MSC_VER +// Special case pid_t on MSVC, because it's a void* rather than an +// integral type. We can't do a global special case because this is already +// dangerous enough (as most pointers will implicitly convert to a void*) +// just doing it for MSVC. +template +void toAppend(const pid_t a, Tgt* res) { + toAppend(uint64_t(a), res); +} +#endif + +/** + * Special version of the call that preallocates exaclty as much memory + * as need for arguments to be stored in target. This means we are + * not doing exponential growth when we append. If you are using it + * in a loop you are aiming at your foot with a big perf-destroying + * bazooka. + * On the other hand if you are appending to a string once, this + * will probably save a few calls to malloc. + */ +template +typename std::enable_if::type>::type>::value>::type +toAppendFit(const Ts&... vs) { + ::folly::detail::reserveInTarget(vs...); toAppend(vs...); } +template +void toAppendFit(const Ts&) {} + /** * Variadic base case: do nothing. */ template -typename std::enable_if::value>::type -toAppend(Tgt* result) { +typename std::enable_if::value>::type toAppend( + Tgt* /* result */) {} + +/** + * Variadic base case: do nothing. + */ +template +typename std::enable_if::value>::type toAppendDelim( + const Delimiter& /* delim */, Tgt* /* result */) {} + +/** + * 1 element: same as toAppend. + */ +template +typename std::enable_if::value>::type toAppendDelim( + const Delimiter& /* delim */, const T& v, Tgt* tgt) { + toAppend(v, tgt); +} + +/** + * Append to string with a delimiter in between elements. Check out + * comments for toAppend for details about memory allocation. + */ +template +typename std::enable_if< + sizeof...(Ts) >= 3 && + IsSomeString::type>::type>::value>::type +toAppendDelim(const Delimiter& delim, const Ts&... vs) { + detail::toAppendDelimStrImpl(delim, vs...); } +/** + * Detail in comment for toAppendFit + */ +template +typename std::enable_if::type>::type>::value>::type +toAppendDelimFit(const Delimiter& delim, const Ts&... vs) { + detail::reserveInTargetDelim(delim, vs...); + toAppendDelim(delim, vs...); +} + +template +void toAppendDelimFit(const De&, const Ts&) {} + /** * to(v1, v2, ...) uses toAppend() (see below) as back-end * for all types. */ template -typename std::enable_if::value, Tgt>::type +typename std::enable_if< + IsSomeString::value && + (sizeof...(Ts) != 1 || + !std::is_same::type>:: + value), + Tgt>::type to(const Ts&... vs) { Tgt result; - toAppend(vs..., &result); + toAppendFit(vs..., &result); return result; } -/******************************************************************************* - * Conversions from string types to integral types. - ******************************************************************************/ - -namespace detail { - /** - * Finds the first non-digit in a string. The number of digits - * searched depends on the precision of the Tgt integral. Assumes the - * string starts with NO whitespace and NO sign. + * Special version of to for floating point. When calling + * folly::to(double), generic implementation above will + * firstly reserve 24 (or 25 when negative value) bytes. This will + * introduce a malloc call for most mainstream string implementations. * - * The semantics of the routine is: - * for (;; ++b) { - * if (b >= e || !isdigit(*b)) return b; - * } + * But for most cases, a floating point doesn't need 24 (or 25) bytes to + * be converted as a string. * - * Complete unrolling marks bottom-line (i.e. entire conversion) - * improvements of 20%. - */ - template - const char* findFirstNonDigit(const char* b, const char* e) { - for (; b < e; ++b) { - auto const c = static_cast(*b) - '0'; - if (c >= 10) break; - } - return b; - } - - // Maximum value of number when represented as a string - template struct MaxString { - static const char*const value; - }; - + * This special version will not do string reserve. + */ +template +typename std::enable_if< + IsSomeString::value && std::is_floating_point::value, + Tgt>::type +to(Src value) { + Tgt result; + toAppend(value, &result); + return result; +} -/* - * Lookup tables that converts from a decimal character value to an integral - * binary value, shifted by a decimal "shift" multiplier. - * For all character values in the range '0'..'9', the table at those - * index locations returns the actual decimal value shifted by the multiplier. - * For all other values, the lookup table returns an invalid OOR value. - */ -// Out-of-range flag value, larger than the largest value that can fit in -// four decimal bytes (9999), but four of these added up together should -// still not overflow uint16_t. -constexpr int32_t OOR = 10000; - -__attribute__((aligned(16))) constexpr uint16_t shift1[] = { - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, // 40 - 1, 2, 3, 4, 5, 6, 7, 8, 9, OOR, OOR, - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 - OOR, OOR, OOR, OOR, OOR, OOR // 250 -}; +/** + * toDelim(SomeString str) returns itself. + */ +template +typename std::enable_if< + IsSomeString::value && + std::is_same::type>::value, + Tgt>::type +toDelim(const Delim& /* delim */, Src&& value) { + return std::forward(value); +} -__attribute__((aligned(16))) constexpr uint16_t shift10[] = { - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, // 40 - 10, 20, 30, 40, 50, 60, 70, 80, 90, OOR, OOR, - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 - OOR, OOR, OOR, OOR, OOR, OOR // 250 -}; +/** + * toDelim(delim, v1, v2, ...) uses toAppendDelim() as + * back-end for all types. + */ +template +typename std::enable_if< + IsSomeString::value && + (sizeof...(Ts) != 1 || + !std::is_same::type>:: + value), + Tgt>::type +toDelim(const Delim& delim, const Ts&... vs) { + Tgt result; + toAppendDelimFit(delim, vs..., &result); + return result; +} -__attribute__((aligned(16))) constexpr uint16_t shift100[] = { - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, // 40 - 100, 200, 300, 400, 500, 600, 700, 800, 900, OOR, OOR, - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 - OOR, OOR, OOR, OOR, OOR, OOR // 250 -}; +/******************************************************************************* + * Conversions from string types to integral types. + ******************************************************************************/ -__attribute__((aligned(16))) constexpr uint16_t shift1000[] = { - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, // 40 - 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, OOR, OOR, - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 - OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 - OOR, OOR, OOR, OOR, OOR, OOR // 250 -}; +namespace detail { -/** - * String represented as a pair of pointers to char to unsigned - * integrals. Assumes NO whitespace before or after, and also that the - * string is composed entirely of digits. Tgt must be unsigned, and no - * sign is allowed in the string (even it's '+'). String may be empty, - * in which case digits_to throws. - */ - template - Tgt digits_to(const char * b, const char * e) { - - static_assert(!std::is_signed::value, "Unsigned type expected"); - assert(b <= e); - - const size_t size = e - b; - - /* Although the string is entirely made of digits, we still need to - * check for overflow. - */ - if (size >= std::numeric_limits::digits10 + 1) { - // Leading zeros? If so, recurse to keep things simple - if (b < e && *b == '0') { - for (++b;; ++b) { - if (b == e) return 0; // just zeros, e.g. "0000" - if (*b != '0') return digits_to(b, e); - } - } - FOLLY_RANGE_CHECK(size == std::numeric_limits::digits10 + 1 && - strncmp(b, detail::MaxString::value, size) <= 0, - "Numeric overflow upon conversion"); - } +Expected str_to_bool(StringPiece* src) noexcept; - // Here we know that the number won't overflow when - // converted. Proceed without checks. +template +Expected str_to_floating(StringPiece* src) noexcept; - Tgt result = 0; +extern template Expected str_to_floating( + StringPiece* src) noexcept; +extern template Expected str_to_floating( + StringPiece* src) noexcept; - for (; e - b >= 4; b += 4) { - result *= 10000; - const int32_t r0 = shift1000[static_cast(b[0])]; - const int32_t r1 = shift100[static_cast(b[1])]; - const int32_t r2 = shift10[static_cast(b[2])]; - const int32_t r3 = shift1[static_cast(b[3])]; - const auto sum = r0 + r1 + r2 + r3; - assert(sum < OOR && "Assumption: string only has digits"); - result += sum; - } +template +Expected digits_to(const char* b, const char* e) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected digits_to( + const char*, + const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +extern template Expected digits_to( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; + +#if FOLLY_HAVE_INT128_T +extern template Expected<__int128, ConversionCode> digits_to<__int128>( + const char*, + const char*) noexcept; +extern template Expected +digits_to(const char*, const char*) noexcept; +#endif - switch (e - b) { - case 3: { - const int32_t r0 = shift100[static_cast(b[0])]; - const int32_t r1 = shift10[static_cast(b[1])]; - const int32_t r2 = shift1[static_cast(b[2])]; - const auto sum = r0 + r1 + r2; - assert(sum < OOR && "Assumption: string only has digits"); - return result * 1000 + sum; - } - case 2: { - const int32_t r0 = shift10[static_cast(b[0])]; - const int32_t r1 = shift1[static_cast(b[1])]; - const auto sum = r0 + r1; - assert(sum < OOR && "Assumption: string only has digits"); - return result * 100 + sum; - } - case 1: { - const int32_t sum = shift1[static_cast(b[0])]; - assert(sum < OOR && "Assumption: string only has digits"); - return result * 10 + sum; - } - } +template +Expected str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +extern template Expected str_to_integral( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; + +#if FOLLY_HAVE_INT128_T +extern template Expected<__int128, ConversionCode> str_to_integral<__int128>( + StringPiece* src) noexcept; +extern template Expected +str_to_integral(StringPiece* src) noexcept; +#endif - assert(b == e); - FOLLY_RANGE_CHECK(size > 0, "Found no digits to convert in input"); - return result; - } +template +typename std:: + enable_if::value, Expected>::type + convertTo(StringPiece* src) noexcept { + return str_to_bool(src); +} +template +typename std::enable_if< + std::is_floating_point::value, + Expected>::type +convertTo(StringPiece* src) noexcept { + return str_to_floating(src); +} - bool str_to_bool(StringPiece * src); +template +typename std::enable_if< + std::is_integral::value && !std::is_same::value, + Expected>::type +convertTo(StringPiece* src) noexcept { + return str_to_integral(src); +} -} // namespace detail +} // namespace detail /** * String represented as a pair of pointers to char to unsigned * integrals. Assumes NO whitespace before or after. */ -template +template typename std::enable_if< - std::is_integral::value && !std::is_signed::value - && !std::is_same::type, bool>::value, - Tgt>::type -to(const char * b, const char * e) { + std::is_integral::value && !std::is_same::value, + Expected>::type +tryTo(const char* b, const char* e) { return detail::digits_to(b, e); } -/** - * String represented as a pair of pointers to char to signed - * integrals. Assumes NO whitespace before or after. Allows an - * optional leading sign. - */ -template +template typename std::enable_if< - std::is_integral::value && std::is_signed::value, - Tgt>::type -to(const char * b, const char * e) { - FOLLY_RANGE_CHECK(b < e, "Empty input string in conversion to integral"); - if (!isdigit(*b)) { - if (*b == '-') { - Tgt result = -to::type>(b + 1, e); - FOLLY_RANGE_CHECK(result <= 0, "Negative overflow."); - return result; - } - FOLLY_RANGE_CHECK(*b == '+', "Invalid lead character"); - ++b; - } - Tgt result = to::type>(b, e); - FOLLY_RANGE_CHECK(result >= 0, "Overflow."); - return result; + std::is_integral::value && !std::is_same::value, + Tgt>::type +to(const char* b, const char* e) { + return tryTo(b, e).thenOrThrow( + [](Tgt res) { return res; }, + [=](ConversionCode code) { + return makeConversionError(code, StringPiece(b, e)); + }); } +/******************************************************************************* + * Conversions from string types to arithmetic types. + ******************************************************************************/ + /** - * Parsing strings to integrals. These routines differ from - * to(string) in that they take a POINTER TO a StringPiece - * and alter that StringPiece to reflect progress information. + * Parsing strings to numeric types. */ +template +FOLLY_NODISCARD inline typename std::enable_if< + std::is_arithmetic::value, + Expected>::type +parseTo(StringPiece src, Tgt& out) { + return detail::convertTo(&src).then( + [&](Tgt res) { return void(out = res), src; }); +} + +/******************************************************************************* + * Integral / Floating Point to integral / Floating Point + ******************************************************************************/ + +namespace detail { /** - * StringPiece to integrals, with progress information. Alters the - * StringPiece parameter to munch the already-parsed characters. + * Bool to integral/float doesn't need any special checks, and this + * overload means we aren't trying to see if a bool is less than + * an integer. */ template typename std::enable_if< - std::is_integral::value - && !std::is_same::type, bool>::value, - Tgt>::type -to(StringPiece * src) { - - auto b = src->data(), past = src->data() + src->size(); - for (;; ++b) { - FOLLY_RANGE_CHECK(b < past, "No digits found in input string"); - if (!isspace(*b)) break; - } + !std::is_same::value && + (std::is_integral::value || std::is_floating_point::value), + Expected>::type +convertTo(const bool& value) noexcept { + return static_cast(value ? 1 : 0); +} - auto m = b; - - // First digit is customized because we test for sign - bool negative = false; - /* static */ if (std::is_signed::value) { - if (!isdigit(*m)) { - if (*m == '-') { - negative = true; - } else { - FOLLY_RANGE_CHECK(*m == '+', "Invalid leading character in conversion" - " to integral"); - } - ++b; - ++m; +/** + * Checked conversion from integral to integral. The checks are only + * performed when meaningful, e.g. conversion from int to long goes + * unchecked. + */ +template +typename std::enable_if< + std::is_integral::value && !std::is_same::value && + !std::is_same::value && + std::is_integral::value, + Expected>::type +convertTo(const Src& value) noexcept { + /* static */ if ( + folly::_t>(std::numeric_limits::max()) < + folly::_t>(std::numeric_limits::max())) { + if (greater_than::max()>(value)) { + return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); } } - FOLLY_RANGE_CHECK(m < past, "No digits found in input string"); - FOLLY_RANGE_CHECK(isdigit(*m), "Non-digit character found"); - m = detail::findFirstNonDigit(m + 1, past); - - Tgt result; - /* static */ if (!std::is_signed::value) { - result = detail::digits_to::type>(b, m); - } else { - auto t = detail::digits_to::type>(b, m); - if (negative) { - result = -t; - FOLLY_RANGE_CHECK(result <= 0, "Negative overflow"); - } else { - result = t; - FOLLY_RANGE_CHECK(result >= 0, "Overflow"); + /* static */ if ( + std::is_signed::value && + (!std::is_signed::value || sizeof(Src) > sizeof(Tgt))) { + if (less_than::min()>(value)) { + return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); } } - src->advance(m - src->data()); - return result; + return static_cast(value); } /** - * StringPiece to bool, with progress information. Alters the - * StringPiece parameter to munch the already-parsed characters. + * Checked conversion from floating to floating. The checks are only + * performed when meaningful, e.g. conversion from float to double goes + * unchecked. */ -template +template typename std::enable_if< - std::is_same::type, bool>::value, - Tgt>::type -to(StringPiece * src) { - return detail::str_to_bool(src); + std::is_floating_point::value && std::is_floating_point::value && + !std::is_same::value, + Expected>::type +convertTo(const Src& value) noexcept { + /* static */ if ( + std::numeric_limits::max() < std::numeric_limits::max()) { + if (value > std::numeric_limits::max()) { + return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); + } + if (value < std::numeric_limits::lowest()) { + return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); + } + } + return static_cast(value); } -namespace detail { - /** - * Enforce that the suffix following a number is made up only of whitespace. + * Check if a floating point value can safely be converted to an + * integer value without triggering undefined behaviour. */ -inline void enforceWhitespace(const char* b, const char* e) { - for (; b != e; ++b) { - FOLLY_RANGE_CHECK(isspace(*b), to("Non-whitespace: ", *b)); +template +inline typename std::enable_if< + std::is_floating_point::value && std::is_integral::value && + !std::is_same::value, + bool>::type +checkConversion(const Src& value) { + constexpr Src tgtMaxAsSrc = static_cast(std::numeric_limits::max()); + constexpr Src tgtMinAsSrc = static_cast(std::numeric_limits::min()); + if (value >= tgtMaxAsSrc) { + if (value > tgtMaxAsSrc) { + return false; + } + const Src mmax = folly::nextafter(tgtMaxAsSrc, Src()); + if (static_cast(value - mmax) > + std::numeric_limits::max() - static_cast(mmax)) { + return false; + } + } else if (std::is_signed::value && value <= tgtMinAsSrc) { + if (value < tgtMinAsSrc) { + return false; + } + const Src mmin = folly::nextafter(tgtMinAsSrc, Src()); + if (static_cast(value - mmin) < + std::numeric_limits::min() - static_cast(mmin)) { + return false; + } } + return true; +} + +// Integers can always safely be converted to floating point values +template +constexpr typename std::enable_if< + std::is_integral::value && std::is_floating_point::value, + bool>::type +checkConversion(const Src&) { + return true; } -} // namespace detail +// Also, floating point values can always be safely converted to bool +// Per the standard, any floating point value that is not zero will yield true +template +constexpr typename std::enable_if< + std::is_floating_point::value && std::is_same::value, + bool>::type +checkConversion(const Src&) { + return true; +} /** - * String or StringPiece to integrals. Accepts leading and trailing - * whitespace, but no non-space trailing characters. + * Checked conversion from integral to floating point and back. The + * result must be convertible back to the source type without loss of + * precision. This seems Draconian but sometimes is what's needed, and + * complements existing routines nicely. For various rounding + * routines, see . */ -template +template typename std::enable_if< - std::is_integral::value, - Tgt>::type -to(StringPiece src) { - Tgt result = to(&src); - detail::enforceWhitespace(src.data(), src.data() + src.size()); - return result; + (std::is_integral::value && std::is_floating_point::value) || + (std::is_floating_point::value && std::is_integral::value), + Expected>::type +convertTo(const Src& value) noexcept { + if (LIKELY(checkConversion(value))) { + Tgt result = static_cast(value); + if (LIKELY(checkConversion(result))) { + Src witness = static_cast(result); + if (LIKELY(value == witness)) { + return result; + } + } + } + return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION); +} + +template +inline std::string errorValue(const Src& value) { +#ifdef FOLLY_HAS_RTTI + return to("(", demangle(typeid(Tgt)), ") ", value); +#else + return to(value); +#endif +} + +template +using IsArithToArith = std::integral_constant< + bool, + !std::is_same::value && !std::is_same::value && + std::is_arithmetic::value && + std::is_arithmetic::value>; + +} // namespace detail + +template +typename std::enable_if< + detail::IsArithToArith::value, + Expected>::type +tryTo(const Src& value) noexcept { + return detail::convertTo(value); +} + +template +typename std::enable_if::value, Tgt>::type to( + const Src& value) { + return tryTo(value).thenOrThrow( + [](Tgt res) { return res; }, + [&](ConversionCode e) { + return makeConversionError(e, detail::errorValue(value)); + }); } /******************************************************************************* - * Conversions from string types to floating-point types. + * Custom Conversions + * + * Any type can be used with folly::to by implementing parseTo. The + * implementation should be provided in the namespace of the type to facilitate + * argument-dependent lookup: + * + * namespace other_namespace { + * ::folly::Expected<::folly::StringPiece, SomeErrorCode> + * parseTo(::folly::StringPiece, OtherType&) noexcept; + * } ******************************************************************************/ +template +FOLLY_NODISCARD typename std::enable_if< + std::is_enum::value, + Expected>::type +parseTo(StringPiece in, T& out) noexcept { + typename std::underlying_type::type tmp{}; + auto restOrError = parseTo(in, tmp); + out = static_cast(tmp); // Harmless if parseTo fails + return restOrError; +} -/** - * StringPiece to double, with progress information. Alters the - * StringPiece parameter to munch the already-parsed characters. - */ -template -inline typename std::enable_if< - std::is_floating_point::value, - Tgt>::type -to(StringPiece *const src) { - using namespace double_conversion; - static StringToDoubleConverter - conv(StringToDoubleConverter::ALLOW_TRAILING_JUNK - | StringToDoubleConverter::ALLOW_LEADING_SPACES, - 0.0, - // return this for junk input string - std::numeric_limits::quiet_NaN(), - nullptr, nullptr); - - FOLLY_RANGE_CHECK(!src->empty(), "No digits found in input string"); - - int length; - auto result = conv.StringToDouble(src->data(), src->size(), - &length); // processed char count - - if (!std::isnan(result)) { - src->advance(length); - return result; - } +FOLLY_NODISCARD +inline Expected parseTo( + StringPiece in, + StringPiece& out) noexcept { + out = in; + return StringPiece{in.end(), in.end()}; +} - for (;; src->advance(1)) { - if (src->empty()) { - throw std::range_error("Unable to convert an empty string" - " to a floating point value."); - } - if (!isspace(src->front())) { - break; - } - } +FOLLY_NODISCARD +inline Expected parseTo( + StringPiece in, + std::string& out) { + out.clear(); + out.append(in.data(), in.size()); // TODO try/catch? + return StringPiece{in.end(), in.end()}; +} - // Was that "inf[inity]"? - if (src->size() >= 3 && toupper((*src)[0]) == 'I' - && toupper((*src)[1]) == 'N' && toupper((*src)[2]) == 'F') { - if (src->size() >= 8 && - toupper((*src)[3]) == 'I' && - toupper((*src)[4]) == 'N' && - toupper((*src)[5]) == 'I' && - toupper((*src)[6]) == 'T' && - toupper((*src)[7]) == 'Y') { - src->advance(8); - } else { - src->advance(3); - } - return std::numeric_limits::infinity(); - } +FOLLY_NODISCARD +inline Expected parseTo( + StringPiece in, + fbstring& out) { + out.clear(); + out.append(in.data(), in.size()); // TODO try/catch? + return StringPiece{in.end(), in.end()}; +} - // Was that "-inf[inity]"? - if (src->size() >= 4 && toupper((*src)[0]) == '-' - && toupper((*src)[1]) == 'I' && toupper((*src)[2]) == 'N' - && toupper((*src)[3]) == 'F') { - if (src->size() >= 9 && - toupper((*src)[4]) == 'I' && - toupper((*src)[5]) == 'N' && - toupper((*src)[6]) == 'I' && - toupper((*src)[7]) == 'T' && - toupper((*src)[8]) == 'Y') { - src->advance(9); - } else { - src->advance(4); - } - return -std::numeric_limits::infinity(); +namespace detail { +template +using ParseToResult = decltype(parseTo(StringPiece{}, std::declval())); + +struct CheckTrailingSpace { + Expected operator()(StringPiece sp) const { + auto e = enforceWhitespaceErr(sp); + if (UNLIKELY(e != ConversionCode::SUCCESS)) + return makeUnexpected(e); + return unit; } +}; - // "nan"? - if (src->size() >= 3 && toupper((*src)[0]) == 'N' - && toupper((*src)[1]) == 'A' && toupper((*src)[2]) == 'N') { - src->advance(3); - return std::numeric_limits::quiet_NaN(); +template +struct ReturnUnit { + template + constexpr Expected operator()(T&&) const { + return unit; } +}; - // "-nan"? - if (src->size() >= 4 && - toupper((*src)[0]) == '-' && - toupper((*src)[1]) == 'N' && - toupper((*src)[2]) == 'A' && - toupper((*src)[3]) == 'N') { - src->advance(4); - return -std::numeric_limits::quiet_NaN(); - } +// Older versions of the parseTo customization point threw on error and +// returned void. Handle that. +template +inline typename std::enable_if< + std::is_void>::value, + Expected>::type +parseToWrap(StringPiece sp, Tgt& out) { + parseTo(sp, out); + return StringPiece(sp.end(), sp.end()); +} - // All bets are off - throw std::range_error("Unable to convert \"" + src->toString() - + "\" to a floating point value."); +template +inline typename std::enable_if< + !std::is_void>::value, + ParseToResult>::type +parseToWrap(StringPiece sp, Tgt& out) { + return parseTo(sp, out); } +template +using ParseToError = ExpectedErrorType()))>; + +} // namespace detail + /** - * Any string, const char*, or StringPiece to double. + * String or StringPiece to target conversion. Accepts leading and trailing + * whitespace, but no non-space trailing characters. */ + template -typename std::enable_if< - std::is_floating_point::value, - Tgt>::type -to(StringPiece src) { - Tgt result = to(&src); - detail::enforceWhitespace(src.data(), src.data() + src.size()); - return result; +inline typename std::enable_if< + !std::is_same::value, + Expected>>::type +tryTo(StringPiece src) { + Tgt result{}; + using Error = detail::ParseToError; + using Check = typename std::conditional< + std::is_arithmetic::value, + detail::CheckTrailingSpace, + detail::ReturnUnit>::type; + return parseTo(src, result).then(Check(), [&](Unit) { + return std::move(result); + }); } -/******************************************************************************* - * Integral to floating point and back - ******************************************************************************/ +template +inline + typename std::enable_if::value, Tgt>::type + to(StringPiece src) { + Tgt result{}; + using Error = detail::ParseToError; + using Check = typename std::conditional< + std::is_arithmetic::value, + detail::CheckTrailingSpace, + detail::ReturnUnit>::type; + auto tmp = detail::parseToWrap(src, result); + return tmp + .thenOrThrow(Check(), [&](Error e) { throw makeConversionError(e, src); }) + .thenOrThrow( + [&](Unit) { return std::move(result); }, + [&](Error e) { throw makeConversionError(e, tmp.value()); }); +} /** - * Checked conversion from integral to flating point and back. The - * result must be convertible back to the source type without loss of - * precision. This seems Draconian but sometimes is what's needed, and - * complements existing routines nicely. For various rounding - * routines, see . + * tryTo/to that take the strings by pointer so the caller gets information + * about how much of the string was consumed by the conversion. These do not + * check for trailing whitepsace. */ -template -typename std::enable_if< - (std::is_integral::value && std::is_floating_point::value) - || - (std::is_floating_point::value && std::is_integral::value), - Tgt>::type -to(const Src & value) { - Tgt result = value; - auto witness = static_cast(result); - if (value != witness) { - throw std::range_error( - to("to<>: loss of precision when converting ", value, - " to type ", typeid(Tgt).name()).c_str()); - } - return result; +template +Expected> tryTo(StringPiece* src) { + Tgt result; + return parseTo(*src, result).then([&, src](StringPiece sp) -> Tgt { + *src = sp; + return std::move(result); + }); +} + +template +Tgt to(StringPiece* src) { + Tgt result{}; + using Error = detail::ParseToError; + return parseTo(*src, result) + .thenOrThrow( + [&, src](StringPiece sp) -> Tgt { + *src = sp; + return std::move(result); + }, + [=](Error e) { return makeConversionError(e, *src); }); } /******************************************************************************* * Enum to anything and back ******************************************************************************/ -#if defined(__GNUC__) && __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7) -// std::underlying_type became available by gcc 4.7.0 - template -typename std::enable_if::value, Tgt>::type -to(const Src & value) { - return to(static_cast::type>(value)); +typename std::enable_if< + std::is_enum::value && !std::is_same::value, + Expected>::type +tryTo(const Src& value) { + using I = typename std::underlying_type::type; + return tryTo(static_cast(value)); } template -typename std::enable_if::value, Tgt>::type -to(const Src & value) { - return static_cast(to::type>(value)); +typename std::enable_if< + std::is_enum::value && !std::is_same::value, + Expected>::type +tryTo(const Src& value) { + using I = typename std::underlying_type::type; + return tryTo(value).then([](I i) { return static_cast(i); }); } -#else - template -typename std::enable_if::value, Tgt>::type -to(const Src & value) { - /* static */ if (Src(-1) < 0) { - /* static */ if (sizeof(Src) <= sizeof(int)) { - return to(static_cast(value)); - } else { - return to(static_cast(value)); - } - } else { - /* static */ if (sizeof(Src) <= sizeof(int)) { - return to(static_cast(value)); - } else { - return to(static_cast(value)); - } - } +typename std::enable_if< + std::is_enum::value && !std::is_same::value, + Tgt>::type +to(const Src& value) { + return to(static_cast::type>(value)); } template -typename std::enable_if::value, Tgt>::type +typename std::enable_if< + std::is_enum::value && !std::is_same::value, Tgt>::type to(const Src & value) { - /* static */ if (Tgt(-1) < 0) { - /* static */ if (sizeof(Tgt) <= sizeof(int)) { - return static_cast(to(value)); - } else { - return static_cast(to(value)); - } - } else { - /* static */ if (sizeof(Tgt) <= sizeof(int)) { - return static_cast(to(value)); - } else { - return static_cast(to(value)); - } - } + return static_cast(to::type>(value)); } -#endif // gcc 4.7 onwards - } // namespace folly - -// FOLLY_CONV_INTERNAL is defined by Conv.cpp. Keep the FOLLY_RANGE_CHECK -// macro for use in Conv.cpp, but #undefine it everywhere else we are included, -// to avoid defining this global macro name in other files that include Conv.h. -#ifndef FOLLY_CONV_INTERNAL -#undef FOLLY_RANGE_CHECK -#endif - -#endif /* FOLLY_BASE_CONV_H_ */