Commit 99cae65e authored by Lewis Baker's avatar Lewis Baker Committed by Facebook Github Bot

Add folly::Try<T>::emplace(), emplaceException() and tryEmplace()

Summary:
Add some methods to `folly::Try<T>` to allow the user to in-place construct a value or exception_wrapper inside the Try object at some point after the Try object was constructed.

Previously, you had to construct a new temporary `Try<T>` object from the value and move-assign the value.

The difference between `t.emplace(args...)` and `tryEmplace(t, args...)` is that the latter catches any exceptions thrown by the constructor and stores the exception in the Try object whereas the former lets the exception propagate out to the caller and leaves the Try object in an empty state.

The `Try<void>` implementation now only conditionally constructs the contained exception_wrapper only if hasValue_ is false. This was needed to be able to have the `.emplaceException()` method correctly in-place construct the `exception_wrapper` object.

Reviewed By: yfeldblum

Differential Revision: D9352439

fbshipit-source-id: f34a4479acb2f93aed11f9626b3152511386bfea
parent 3be01dde
......@@ -115,6 +115,26 @@ Try<T>::~Try() {
}
}
template <typename T>
template <typename... Args>
T& Try<T>::emplace(Args&&... args) noexcept(
std::is_nothrow_constructible<T, Args&&...>::value) {
this->destroy();
new (&value_) T(static_cast<Args&&>(args)...);
contains_ = Contains::VALUE;
return value_;
}
template <typename T>
template <typename... Args>
exception_wrapper& Try<T>::emplaceException(Args&&... args) noexcept(
std::is_nothrow_constructible<exception_wrapper, Args&&...>::value) {
this->destroy();
new (&e_) exception_wrapper(static_cast<Args&&>(args)...);
contains_ = Contains::EXCEPTION;
return e_;
}
template <class T>
T& Try<T>::value() & {
throwIfFailed();
......@@ -161,8 +181,36 @@ void Try<T>::destroy() noexcept {
}
}
Try<void>& Try<void>::operator=(const Try<void>& t) noexcept {
if (t.hasException()) {
if (hasException()) {
e_ = t.e_;
} else {
new (&e_) exception_wrapper(t.e_);
hasValue_ = false;
}
} else {
if (hasException()) {
e_.~exception_wrapper();
hasValue_ = true;
}
}
return *this;
}
template <typename... Args>
exception_wrapper& Try<void>::emplaceException(Args&&... args) noexcept(
std::is_nothrow_constructible<exception_wrapper, Args&&...>::value) {
if (hasException()) {
e_.~exception_wrapper();
}
new (&e_) exception_wrapper(static_cast<Args&&>(args)...);
hasValue_ = false;
return e_;
}
void Try<void>::throwIfFailed() const {
if (!hasValue_) {
if (hasException()) {
e_.throw_exception();
}
}
......@@ -196,6 +244,23 @@ typename std::
}
}
template <typename T, typename... Args>
T* tryEmplace(Try<T>& t, Args&&... args) noexcept {
try {
return std::addressof(t.emplace(static_cast<Args&&>(args)...));
} catch (const std::exception& ex) {
t.emplaceException(std::current_exception(), ex);
return nullptr;
} catch (...) {
t.emplaceException(std::current_exception());
return nullptr;
}
}
void tryEmplace(Try<void>& t) noexcept {
t.emplace();
}
namespace try_detail {
/**
......
......@@ -120,6 +120,34 @@ class Try {
~Try();
/*
* In-place construct the value in the Try object.
*
* Destroys any previous value prior to constructing the new value.
* Leaves *this in an empty state if the construction of T throws.
*
* @returns reference to the newly constructed value.
*/
template <typename... Args>
T& emplace(Args&&... args) noexcept(
std::is_nothrow_constructible<T, Args&&...>::value);
/*
* In-place construct an exception in the Try object.
*
* Destroys any previous value prior to constructing the new value.
* Leaves *this in an empty state if the construction of the exception_wrapper
* throws.
*
* Any arguments passed to emplaceException() are forwarded on to the
* exception_wrapper constructor.
*
* @returns reference to the newly constructed exception_wrapper.
*/
template <typename... Args>
exception_wrapper& emplaceException(Args&&... args) noexcept(
std::is_nothrow_constructible<exception_wrapper, Args&&...>::value);
/*
* Get a mutable reference to the contained value. If the Try contains an
* exception it will be rethrown.
......@@ -364,15 +392,49 @@ class Try<void> {
e_(std::move(e)){}
// Copy assigner
Try& operator=(const Try<void>& t) noexcept {
hasValue_ = t.hasValue_;
e_ = t.e_;
return *this;
}
inline Try& operator=(const Try<void>& t) noexcept;
// Copy constructor
Try(const Try<void>& t) noexcept {
*this = t;
Try(const Try<void>& t) noexcept : hasValue_(t.hasValue_) {
if (t.hasException()) {
new (&e_) exception_wrapper(t.e_);
}
}
~Try() {
if (hasException()) {
e_.~exception_wrapper();
}
}
/*
* In-place construct a 'void' value into this Try object.
*
* This has the effect of clearing any existing exception stored in the
* Try object.
*/
void emplace() noexcept {
if (hasException()) {
e_.~exception_wrapper();
hasValue_ = true;
}
}
/*
* In-place construct an exception in the Try object.
*
* Destroys any previous value prior to constructing the new value.
* Leaves *this in an empty state if the construction of the exception_wrapper
* throws.
*
* Any arguments passed to emplaceException() are forwarded on to the
* exception_wrapper constructor.
*
* @returns reference to the newly constructed exception_wrapper.
*/
template <typename... Args>
exception_wrapper& emplaceException(Args&&... args) noexcept(
std::is_nothrow_constructible<exception_wrapper, Args&&...>::value);
// If the Try contains an exception, throws it
void value() const { throwIfFailed(); }
......@@ -503,7 +565,9 @@ class Try<void> {
private:
bool hasValue_;
union {
exception_wrapper e_;
};
};
/*
......@@ -529,6 +593,22 @@ typename std::
enable_if<std::is_same<invoke_result_t<F>, void>::value, Try<void>>::type
makeTryWith(F&& f);
/*
* Try to in-place construct a new value from the specified arguments.
*
* If T's constructor throws an exception then this is caught and the Try<T>
* object is initialised to hold that exception.
*
* @param args Are passed to T's constructor.
*/
template <typename T, typename... Args>
T* tryEmplace(Try<T>& t, Args&&... args) noexcept;
/*
* Overload of tryEmplace() for Try<void>.
*/
inline void tryEmplace(Try<void>& t) noexcept;
/**
* Tuple<Try<Type>...> -> std::tuple<Type...>
*
......
......@@ -172,6 +172,120 @@ TEST(Try, assignmentWithThrowingMoveConstructor) {
EXPECT_EQ(0, counter);
}
TEST(Try, emplace) {
Try<A> t;
A& t_a = t.emplace(10);
EXPECT_TRUE(t.hasValue());
EXPECT_EQ(t_a.x(), 10);
}
TEST(Try, emplaceWithThrowingConstructor) {
struct MyException : std::exception {};
struct ThrowingConstructor {
explicit ThrowingConstructor(bool shouldThrow) {
if (shouldThrow) {
throw MyException{};
}
}
};
{
// Try constructing from empty state to new value and constructor throws.
Try<ThrowingConstructor> t;
EXPECT_FALSE(t.hasValue());
EXPECT_FALSE(t.hasException());
EXPECT_THROW(t.emplace(true), MyException);
EXPECT_FALSE(t.hasValue());
EXPECT_FALSE(t.hasException());
}
{
// Initialise to value, then re-emplace with throwing constructor.
// This should reset the object back to empty.
Try<ThrowingConstructor> t{in_place, false};
EXPECT_TRUE(t.hasValue());
EXPECT_THROW(t.emplace(true), MyException);
EXPECT_FALSE(t.hasValue());
EXPECT_FALSE(t.hasException());
}
}
TEST(Try, tryEmplace) {
Try<A> t;
A* a = tryEmplace(t, 10);
EXPECT_EQ(&t.value(), a);
EXPECT_TRUE(t.hasValue());
EXPECT_EQ(10, t.value().x());
}
TEST(Try, tryEmplaceWithThrowingConstructor) {
struct MyException : std::exception {};
struct NonInheritingException {};
struct ThrowingConstructor {
[[noreturn]] ThrowingConstructor() noexcept(false) {
throw NonInheritingException{}; // @nolint
}
explicit ThrowingConstructor(bool shouldThrow) {
if (shouldThrow) {
throw MyException{};
}
}
};
{
Try<ThrowingConstructor> t;
EXPECT_EQ(nullptr, tryEmplace(t, true));
EXPECT_TRUE(t.hasException());
EXPECT_NE(t.tryGetExceptionObject<MyException>(), nullptr);
}
{
Try<ThrowingConstructor> t;
EXPECT_EQ(nullptr, tryEmplace(t));
EXPECT_TRUE(t.hasException());
EXPECT_NE(t.tryGetExceptionObject<NonInheritingException>(), nullptr);
}
{
Try<ThrowingConstructor> t;
EXPECT_NE(nullptr, tryEmplace(t, false));
EXPECT_TRUE(t.hasValue());
EXPECT_EQ(nullptr, tryEmplace(t, true));
EXPECT_TRUE(t.hasException());
EXPECT_NE(t.tryGetExceptionObject<MyException>(), nullptr);
}
}
TEST(Try, emplaceVoidTry) {
struct MyException : std::exception {};
Try<void> t;
t.emplace();
EXPECT_TRUE(t.hasValue());
t.emplaceException(folly::in_place_type<MyException>);
EXPECT_FALSE(t.hasValue());
EXPECT_TRUE(t.hasException());
EXPECT_TRUE(t.hasException<MyException>());
t.emplace();
EXPECT_TRUE(t.hasValue());
EXPECT_FALSE(t.hasException());
}
TEST(Try, tryEmplaceVoidTry) {
struct MyException : std::exception {};
Try<void> t;
tryEmplace(t);
EXPECT_TRUE(t.hasValue());
t.emplaceException(folly::in_place_type<MyException>);
EXPECT_FALSE(t.hasValue());
EXPECT_TRUE(t.hasException());
EXPECT_TRUE(t.hasException<MyException>());
t.emplace();
EXPECT_TRUE(t.hasValue());
EXPECT_FALSE(t.hasException());
}
TEST(Try, nothrow) {
using F = HasCtors<false>;
using T = HasCtors<true>;
......
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