Commit 4da4f331 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

Use folly::Expected to implement folly::tryTo, a non-throwing variant of folly::to

Summary:
This change adds a non-throwing interface for folly::to<T>: tryTo<T>, which
returns an Expected<T, ConversionCode>.

Here is how the non-throwing interface compares to the regular interface in
terms of performance. On the successful path, there's generally not much
difference between using the throwing and non-throwing interfaces. For the
error path, tryTo<> is about three orders of magnitude faster than to<>.

Reviewed By: mhx

Differential Revision: D3720512

fbshipit-source-id: dadb8db1b7d7ad8d3e80c1cc69c0480169f9217a
parent 5d242d14
This diff is collapsed.
This diff is collapsed.
......@@ -92,6 +92,15 @@ constexpr bool kHasUnalignedAccess = false;
# define FOLLY_ALWAYS_INLINE inline
#endif
// warn unused result
#if defined(_MSC_VER) && (_MSC_VER >= 1700)
#define FOLLY_WARN_UNUSED_RESULT _Check_return_
#elif defined(__clang__) || defined(__GNUC__)
#define FOLLY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
#else
#define FOLLY_WARN_UNUSED_RESULT
#endif
// target
#ifdef _MSC_VER
# define FOLLY_TARGET_ATTRIBUTE(target)
......
......@@ -328,7 +328,7 @@ bool splitFixed(const Delim& delimiter, StringPiece input, OutputType& output) {
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
return false;
}
parseTo(input, output);
output = folly::to<OutputType>(input);
return true;
}
......@@ -346,7 +346,7 @@ bool splitFixed(
StringPiece tail(input.begin() + cut + detail::delimSize(delimiter),
input.end());
if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
parseTo(head, outHead);
outHead = folly::to<OutputType>(head);
return true;
}
return false;
......
......@@ -491,7 +491,8 @@ struct IsConvertible {
template <class T>
struct IsConvertible<
T,
decltype(parseTo(std::declval<folly::StringPiece>(), std::declval<T&>()))> {
decltype(static_cast<void>(
parseTo(std::declval<folly::StringPiece>(), std::declval<T&>())))> {
enum { value = true };
};
......
......@@ -215,3 +215,31 @@ is returned, which can be tested for as follows:
// string could not be parsed
}
```
#### Non-throwing interfaces
`tryTo<T>` is the non-throwing variant of `to<T>`. It returns
an `Expected<T, ConversionCode>`. You can think of `Expected`
as like an `Optional<T>`, but if the conversion failed, `Expected`
stores an error code instead of a `T`.
`tryTo<T>` has similar performance as `to<T>` when the
conversion is successful. On the error path, you can expect
`tryTo<T>` to be roughly three orders of magnitude faster than
the throwing `to<T>` and to completely avoid any lock contention
arising from stack unwinding.
Here is how to use non-throwing conversions:
``` Cpp
auto t1 = tryTo<int>(str);
if (t1.hasValue()) {
use(t1.value());
}
```
`Expected` has a composability feature to make the above pattern simpler.
``` Cpp
tryTo<int>(str).then([](int i) { use(i); });
```
......@@ -911,6 +911,16 @@ inline void stringToTypeClassic(const char* str, uint32_t n) {
}
}
template <typename T>
inline void stringToTypeOptional(const char* str, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
auto val = tryTo<T>(str);
if (val.hasValue()) {
doNotOptimizeAway(val.value());
}
}
}
template <typename T>
inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
......@@ -924,6 +934,16 @@ inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) {
}
}
template <typename T>
inline void ptrPairToIntOptional(StringPiece sp, uint32_t n) {
for (uint32_t i = 0; i < n; ++i) {
auto val = tryTo<T>(sp.begin(), sp.end());
if (val.hasValue()) {
doNotOptimizeAway(val.value());
}
}
}
constexpr uint32_t kArithNumIter = 10000;
template <typename T, typename U>
......@@ -944,6 +964,24 @@ inline size_t arithToArithClassic(const U* in, uint32_t numItems) {
return kArithNumIter * numItems;
}
template <typename T, typename U>
inline size_t arithToArithOptional(const U* in, uint32_t numItems) {
for (uint32_t i = 0; i < kArithNumIter; ++i) {
for (uint32_t j = 0; j < numItems; ++j) {
auto val = tryTo<T>(*in);
doNotOptimizeAway(val.hasValue());
if (val.hasValue()) {
auto v2 = val.value();
doNotOptimizeAway(v2);
}
doNotOptimizeAway(j);
}
doNotOptimizeAway(i);
}
return kArithNumIter * numItems;
}
} // namespace
namespace folly {
......@@ -980,6 +1018,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \
BENCHMARK(stringTo##name##ClassicError, n) { \
stringToTypeClassic<type>(fail, n); \
} \
BENCHMARK(stringTo##name##Optional, n) { \
stringToTypeOptional<type>(pass, n); \
} \
BENCHMARK(stringTo##name##OptionalError, n) { \
stringToTypeOptional<type>(fail, n); \
}
#define PTR_PAIR_TO_INT_BENCHMARK(type, name, pass, fail) \
......@@ -988,6 +1032,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \
BENCHMARK(ptrPairTo##name##ClassicError, n) { \
ptrPairToIntClassic<type>(fail, n); \
} \
BENCHMARK(ptrPairTo##name##Optional, n) { \
ptrPairToIntOptional<type>(pass, n); \
} \
BENCHMARK(ptrPairTo##name##OptionalError, n) { \
ptrPairToIntOptional<type>(fail, n); \
}
#define ARITH_TO_ARITH_BENCHMARK(type, name, pass, fail) \
......@@ -996,6 +1046,12 @@ std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} \
BENCHMARK_MULTI(name##ClassicError) { \
return arithToArithClassic<type>(fail.data(), fail.size()); \
} \
BENCHMARK_MULTI(name##Optional) { \
return arithToArithOptional<type>(pass.data(), pass.size()); \
} \
BENCHMARK_MULTI(name##OptionalError) { \
return arithToArithOptional<type>(fail.data(), fail.size()); \
}
#define INT_TO_ARITH_BENCHMARK(type, name, pass, fail) \
......
......@@ -18,6 +18,7 @@
#include <folly/Conv.h>
#include <folly/Foreach.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <limits>
#include <sstream>
#include <stdexcept>
......@@ -820,6 +821,14 @@ TEST(Conv, StringToBool) {
EXPECT_EQ(buf5, sp5.begin());
}
TEST(Conv, Transform) {
const std::vector<int64_t> in{1, 2, 3};
std::vector<std::string> out(in.size());
std::transform(in.begin(), in.end(), out.begin(), to<std::string, int64_t>);
const std::vector<std::string> ref{"1", "2", "3"};
EXPECT_EQ(ref, out);
}
TEST(Conv, FloatToInt) {
EXPECT_EQ(to<int>(42.0f), 42);
EXPECT_EQ(to<int8_t>(-128.0f), int8_t(-128));
......@@ -874,7 +883,7 @@ template <typename F>
void testConvError(
F&& expr,
const char* exprStr,
ConversionError::Code code,
ConversionCode code,
const char* value,
bool quotedValue,
int line) {
......@@ -907,7 +916,7 @@ void testConvError(
testConvError( \
[&] { return expr; }, \
#expr, \
ConversionError::code, \
ConversionCode::code, \
value, \
quoted, \
__LINE__)
......@@ -1026,6 +1035,91 @@ TEST(Conv, ConversionErrorFloatToInt) {
EXPECT_CONV_ERROR_ARITH(int8_t, 65.5, ARITH_LOSS_OF_PRECISION);
}
TEST(Conv, TryStringToBool) {
auto rv1 = folly::tryTo<bool>("xxxx");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<bool>("false");
EXPECT_TRUE(rv2.hasValue());
EXPECT_FALSE(rv2.value());
auto rv3 = folly::tryTo<bool>("yes");
EXPECT_TRUE(rv3.hasValue());
EXPECT_TRUE(rv3.value());
}
TEST(Conv, TryStringToInt) {
auto rv1 = folly::tryTo<int>("1000000000000000000000000000000");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<int>("4711");
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 4711);
}
TEST(Conv, TryStringToFloat) {
auto rv1 = folly::tryTo<float>("");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<float>("3.14");
EXPECT_TRUE(rv2.hasValue());
EXPECT_NEAR(rv2.value(), 3.14, 1e-5);
}
TEST(Conv, TryStringToDouble) {
auto rv1 = folly::tryTo<double>("");
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<double>("3.14");
EXPECT_TRUE(rv2.hasValue());
EXPECT_NEAR(rv2.value(), 3.14, 1e-10);
}
TEST(Conv, TryIntToInt) {
auto rv1 = folly::tryTo<uint8_t>(256);
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<uint8_t>(255);
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 255);
}
TEST(Conv, TryFloatToFloat) {
auto rv1 = folly::tryTo<float>(1e100);
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<double>(25.5f);
EXPECT_TRUE(rv2.hasValue());
EXPECT_NEAR(rv2.value(), 25.5, 1e-10);
}
TEST(Conv, TryFloatToInt) {
auto rv1 = folly::tryTo<int>(100.001);
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<int>(100.0);
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 100);
}
TEST(Conv, TryIntToFloat) {
auto rv1 = folly::tryTo<float>(std::numeric_limits<uint64_t>::max());
EXPECT_FALSE(rv1.hasValue());
auto rv2 = folly::tryTo<float>(1000ULL);
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 1000.0f);
}
TEST(Conv, TryPtrPairToInt) {
StringPiece sp1("1000000000000000000000000000000");
auto rv1 = folly::tryTo<int>(sp1.begin(), sp1.end());
EXPECT_FALSE(rv1.hasValue());
StringPiece sp2("4711");
auto rv2 = folly::tryTo<int>(sp2.begin(), sp2.end());
EXPECT_TRUE(rv2.hasValue());
EXPECT_EQ(rv2.value(), 4711);
StringPiece sp3("-4711");
auto rv3 = folly::tryTo<int>(sp3.begin(), sp3.end());
EXPECT_TRUE(rv3.hasValue());
EXPECT_EQ(rv3.value(), -4711);
StringPiece sp4("4711");
auto rv4 = folly::tryTo<uint16_t>(sp4.begin(), sp4.end());
EXPECT_TRUE(rv4.hasValue());
EXPECT_EQ(rv4.value(), 4711);
}
TEST(Conv, NewUint64ToString) {
char buf[21];
......@@ -1091,10 +1185,12 @@ struct Dimensions {
}
};
void parseTo(folly::StringPiece in, Dimensions& out) {
out.w = folly::to<int>(&in);
in.removePrefix("x");
out.h = folly::to<int>(&in);
Expected<StringPiece, ConversionCode> parseTo(
folly::StringPiece in,
Dimensions& out) {
return parseTo(in, out.w)
.then([](StringPiece sp) { return sp.removePrefix("x"), sp; })
.then([&](StringPiece sp) { return parseTo(sp, out.h); });
}
template <class String>
......@@ -1117,3 +1213,10 @@ TEST(Conv, custom_kkproviders) {
EXPECT_GT(str.capacity(), 2000);
EXPECT_LT(str.capacity(), 2500);
}
TEST(Conv, TryToThenWithVoid) {
auto x = tryTo<int>("42").then([](int) {});
EXPECT_TRUE(x.hasValue());
Unit u = x.value();
(void)u;
}
......@@ -921,14 +921,27 @@ enum class Color {
Blue,
};
void parseTo(folly::StringPiece in, Color& out) {
enum class ColorErrorCode { INVALID_COLOR };
struct ColorError : std::runtime_error {
using std::runtime_error::runtime_error;
};
ColorError makeConversionError(ColorErrorCode, StringPiece sp) {
return ColorError("Invalid my::Color representation : " + sp.str());
}
Expected<StringPiece, ColorErrorCode> parseTo(
StringPiece in,
Color& out) noexcept {
if (in == "R") {
out = Color::Red;
} else if (in == "B") {
out = Color::Blue;
} else {
throw runtime_error("");
return makeUnexpected(ColorErrorCode::INVALID_COLOR);
}
return StringPiece(in.end(), in.end());
}
}
......@@ -938,6 +951,8 @@ TEST(Split, fixed_convert_custom) {
EXPECT_TRUE(folly::split(',', "R,B", c1, c2));
EXPECT_EQ(c1, my::Color::Red);
EXPECT_EQ(c2, my::Color::Blue);
EXPECT_THROW(folly::split(',', "B,G", c1, c2), my::ColorError);
}
TEST(String, join) {
......
......@@ -52,7 +52,7 @@ TEST(Traits, scalars) {
TEST(Traits, containers) {
EXPECT_TRUE (IsRelocatable<vector<F1>>::value);
EXPECT_TRUE ((IsRelocatable<pair<F1, F1>>::value));
EXPECT_TRUE((IsRelocatable<pair<F1, F1>>::value));
EXPECT_TRUE ((IsRelocatable<pair<T1, T2>>::value));
}
......
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