[Support] Port ErrorOr<T> from lld to C++03.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@172991 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Michael J. Spencer 2013-01-20 20:32:30 +00:00
parent 5ff7a3f947
commit 01812bebcc
5 changed files with 452 additions and 0 deletions

View File

@ -42,6 +42,33 @@
#define LLVM_HAS_RVALUE_REFERENCE_THIS 0
#endif
/// \macro LLVM_HAS_CXX11_TYPETRAITS
/// \brief Does the compiler have the C++11 type traits.
///
/// #include <type_traits>
///
/// * enable_if
/// * {true,false}_type
/// * is_constructible
/// * etc...
#if defined(__GXX_EXPERIMENTAL_CXX0X__) \
|| (defined(_MSC_VER) && _MSC_VER >= 1600)
#define LLVM_HAS_CXX11_TYPETRAITS 1
#else
#define LLVM_HAS_CXX11_TYPETRAITS 0
#endif
/// \macro LLVM_HAS_CXX11_STDLIB
/// \brief Does the compiler have the C++11 standard library.
///
/// Implies LLVM_HAS_RVALUE_REFERENCES, LLVM_HAS_CXX11_TYPETRAITS
#if defined(__GXX_EXPERIMENTAL_CXX0X__) \
|| (defined(_MSC_VER) && _MSC_VER >= 1600)
#define LLVM_HAS_CXX11_STDLIB 1
#else
#define LLVM_HAS_CXX11_STDLIB 0
#endif
/// llvm_move - Expands to ::std::move if the compiler supports
/// r-value references; otherwise, expands to the argument.
#if LLVM_HAS_RVALUE_REFERENCES

View File

@ -0,0 +1,342 @@
//===- llvm/Support/ErrorOr.h - Error Smart Pointer -----------------------===//
//
// The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// Provides ErrorOr<T> smart pointer.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_SUPPORT_ERROR_OR_H
#define LLVM_SUPPORT_ERROR_OR_H
#include "llvm/Support/AlignOf.h"
#include "llvm/Support/system_error.h"
#include "llvm/Support/type_traits.h"
#include <cassert>
#if LLVM_HAS_CXX11_TYPETRAITS
#include <type_traits>
#endif
namespace llvm {
struct ErrorHolderBase {
error_code Error;
uint16_t RefCount;
bool HasUserData;
ErrorHolderBase() : RefCount(1) {}
void aquire() {
++RefCount;
}
void release() {
if (--RefCount == 0)
delete this;
}
protected:
virtual ~ErrorHolderBase() {}
};
template<class T>
struct ErrorHolder : ErrorHolderBase {
#if LLVM_HAS_RVALUE_REFERENCES
ErrorHolder(T &&UD) : UserData(llvm_move(UD)) {}
#else
ErrorHolder(T &UD) : UserData(UD) {}
#endif
T UserData;
};
template<class Tp> struct ErrorOrUserDataTraits : llvm::false_type {};
#if LLVM_HAS_CXX11_TYPETRAITS && LLVM_HAS_RVALUE_REFERENCES
template<class T, class V>
typename std::enable_if< std::is_constructible<T, V>::value
, typename std::remove_reference<V>::type>::type &&
moveIfMoveConstructible(V &Val) {
return std::move(Val);
}
template<class T, class V>
typename std::enable_if< !std::is_constructible<T, V>::value
, typename std::remove_reference<V>::type>::type &
moveIfMoveConstructible(V &Val) {
return Val;
}
#else
template<class T, class V>
V &moveIfMoveConstructible(V &Val) {
return Val;
}
#endif
/// \brief Stores a reference that can be changed.
template <typename T>
class ReferenceStorage {
T *Storage;
public:
ReferenceStorage(T &Ref) : Storage(&Ref) {}
operator T &() const { return *Storage; }
T &get() const { return *Storage; }
};
/// \brief Represents either an error or a value T.
///
/// ErrorOr<T> is a pointer-like class that represents the result of an
/// operation. The result is either an error, or a value of type T. This is
/// designed to emulate the usage of returning a pointer where nullptr indicates
/// failure. However instead of just knowing that the operation failed, we also
/// have an error_code and optional user data that describes why it failed.
///
/// It is used like the following.
/// \code
/// ErrorOr<Buffer> getBuffer();
/// void handleError(error_code ec);
///
/// auto buffer = getBuffer();
/// if (!buffer)
/// handleError(buffer);
/// buffer->write("adena");
/// \endcode
///
/// ErrorOr<T> also supports user defined data for specific error_codes. To use
/// this feature you must first add a template specialization of
/// ErrorOrUserDataTraits derived from std::true_type for your type in the lld
/// namespace. This specialization must have a static error_code error()
/// function that returns the error_code this data is used with.
///
/// getError<UserData>() may be called to get either the stored user data, or
/// a default constructed UserData if none was stored.
///
/// Example:
/// \code
/// struct InvalidArgError {
/// InvalidArgError() {}
/// InvalidArgError(std::string S) : ArgName(S) {}
/// std::string ArgName;
/// };
///
/// namespace llvm {
/// template<>
/// struct ErrorOrUserDataTraits<InvalidArgError> : std::true_type {
/// static error_code error() {
/// return make_error_code(errc::invalid_argument);
/// }
/// };
/// } // end namespace llvm
///
/// using namespace llvm;
///
/// ErrorOr<int> foo() {
/// return InvalidArgError("adena");
/// }
///
/// int main() {
/// auto a = foo();
/// if (!a && error_code(a) == errc::invalid_argument)
/// llvm::errs() << a.getError<InvalidArgError>().ArgName << "\n";
/// }
/// \endcode
///
/// An implicit conversion to bool provides a way to check if there was an
/// error. The unary * and -> operators provide pointer like access to the
/// value. Accessing the value when there is an error has undefined behavior.
///
/// When T is a reference type the behaivor is slightly different. The reference
/// is held in a std::reference_wrapper<std::remove_reference<T>::type>, and
/// there is special handling to make operator -> work as if T was not a
/// reference.
///
/// T cannot be a rvalue reference.
template<class T>
class ErrorOr {
static const bool isRef = is_reference<T>::value;
typedef ReferenceStorage<typename remove_reference<T>::type> wrap;
public:
typedef typename
conditional< isRef
, wrap
, T
>::type storage_type;
private:
typedef T &reference;
typedef typename remove_reference<T>::type *pointer;
public:
ErrorOr() : IsValid(false) {}
ErrorOr(llvm::error_code EC) : HasError(true), IsValid(true) {
Error = new ErrorHolderBase;
Error->Error = EC;
Error->HasUserData = false;
}
template<class UserDataT>
ErrorOr(UserDataT UD, typename
enable_if_c<ErrorOrUserDataTraits<UserDataT>::value>::type* = 0)
: HasError(true), IsValid(true) {
Error = new ErrorHolder<UserDataT>(llvm_move(UD));
Error->Error = ErrorOrUserDataTraits<UserDataT>::error();
Error->HasUserData = true;
}
ErrorOr(T Val) : HasError(false), IsValid(true) {
new (get()) storage_type(moveIfMoveConstructible<storage_type>(Val));
}
ErrorOr(const ErrorOr &Other) : IsValid(false) {
// Construct an invalid ErrorOr if other is invalid.
if (!Other.IsValid)
return;
if (!Other.HasError) {
// Get the other value.
new (get()) storage_type(*Other.get());
HasError = false;
} else {
// Get other's error.
Error = Other.Error;
HasError = true;
Error->aquire();
}
IsValid = true;
}
ErrorOr &operator =(const ErrorOr &Other) {
if (this == &Other)
return *this;
this->~ErrorOr();
new (this) ErrorOr(Other);
return *this;
}
#if LLVM_HAS_RVALUE_REFERENCES
ErrorOr(ErrorOr &&Other) : IsValid(false) {
// Construct an invalid ErrorOr if other is invalid.
if (!Other.IsValid)
return;
if (!Other.HasError) {
// Get the other value.
IsValid = true;
new (get()) storage_type(std::move(*Other.get()));
HasError = false;
// Tell other not to do any destruction.
Other.IsValid = false;
} else {
// Get other's error.
Error = Other.Error;
HasError = true;
// Tell other not to do any destruction.
Other.IsValid = false;
}
IsValid = true;
}
ErrorOr &operator =(ErrorOr &&Other) {
if (this == &Other)
return *this;
this->~ErrorOr();
new (this) ErrorOr(std::move(Other));
return *this;
}
~ErrorOr() {
if (!IsValid)
return;
if (HasError)
Error->release();
else
get()->~storage_type();
}
#endif
template<class ET>
ET getError() const {
assert(IsValid && "Cannot get the error of a default constructed ErrorOr!");
assert(HasError && "Cannot get an error if none exists!");
assert(ErrorOrUserDataTraits<ET>::error() == Error->Error &&
"Incorrect user error data type for error!");
if (!Error->HasUserData)
return ET();
return reinterpret_cast<const ErrorHolder<ET>*>(Error)->UserData;
}
typedef void (*unspecified_bool_type)();
static void unspecified_bool_true() {}
/// \brief Return false if there is an error.
operator unspecified_bool_type() const {
assert(IsValid && "Can't do anything on a default constructed ErrorOr!");
return HasError ? 0 : unspecified_bool_true;
}
operator llvm::error_code() const {
assert(IsValid && "Can't do anything on a default constructed ErrorOr!");
return HasError ? Error->Error : llvm::error_code::success();
}
pointer operator ->() {
return toPointer(get());
}
reference operator *() {
return *get();
}
private:
pointer toPointer(pointer Val) {
return Val;
}
pointer toPointer(wrap *Val) {
return &Val->get();
}
protected:
storage_type *get() {
assert(IsValid && "Can't do anything on a default constructed ErrorOr!");
assert(!HasError && "Cannot get value when an error exists!");
return reinterpret_cast<storage_type*>(TStorage.buffer);
}
const storage_type *get() const {
assert(IsValid && "Can't do anything on a default constructed ErrorOr!");
assert(!HasError && "Cannot get value when an error exists!");
return reinterpret_cast<const storage_type*>(TStorage.buffer);
}
union {
AlignedCharArrayUnion<storage_type> TStorage;
ErrorHolderBase *Error;
};
bool HasError : 1;
bool IsValid : 1;
};
template<class T, class E>
typename enable_if_c<is_error_code_enum<E>::value ||
is_error_condition_enum<E>::value, bool>::type
operator ==(ErrorOr<T> &Err, E Code) {
return error_code(Err) == Code;
}
} // end namespace llvm
#endif

View File

@ -145,6 +145,10 @@ template <typename T> struct is_pointer<T* const> : true_type {};
template <typename T> struct is_pointer<T* volatile> : true_type {};
template <typename T> struct is_pointer<T* const volatile> : true_type {};
/// \brief Metafunction that determines wheather the given type is a reference.
template <typename T> struct is_reference : false_type {};
template <typename T> struct is_reference<T&> : true_type {};
/// \brief Metafunction that determines whether the given type is either an
/// integral type or an enumeration type.
///

View File

@ -13,6 +13,7 @@ add_llvm_unittest(SupportTests
ConstantRangeTest.cpp
DataExtractorTest.cpp
EndianTest.cpp
ErrorOrTest.cpp
FileOutputBufferTest.cpp
IntegersSubsetTest.cpp
LeakDetectorTest.cpp

View File

@ -0,0 +1,78 @@
//===- unittests/ErrorOrTest.cpp - ErrorOr.h tests ------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/ErrorOr.h"
#include "gtest/gtest.h"
#include <memory>
using namespace llvm;
namespace {
ErrorOr<int> t1() {return 1;}
ErrorOr<int> t2() {return make_error_code(errc::invalid_argument);}
TEST(ErrorOr, SimpleValue) {
ErrorOr<int> a = t1();
EXPECT_TRUE(a);
EXPECT_EQ(1, *a);
a = t2();
EXPECT_FALSE(a);
EXPECT_EQ(errc::invalid_argument, a);
EXPECT_DEBUG_DEATH(*a, "Cannot get value when an error exists");
}
#if LLVM_HAS_CXX11_STDLIB
ErrorOr<std::unique_ptr<int> > t3() {
return std::unique_ptr<int>(new int(3));
}
#endif
TEST(ErrorOr, Types) {
int x;
ErrorOr<int&> a(x);
*a = 42;
EXPECT_EQ(42, x);
#if LLVM_HAS_CXX11_STDLIB
// Move only types.
EXPECT_EQ(3, **t3());
#endif
}
} // end anon namespace
struct InvalidArgError {
InvalidArgError() {}
InvalidArgError(std::string S) : ArgName(S) {}
std::string ArgName;
};
namespace llvm {
template<>
struct ErrorOrUserDataTraits<InvalidArgError> : std::true_type {
static error_code error() {
return make_error_code(errc::invalid_argument);
}
};
} // end namespace lld
ErrorOr<int> t4() {
return InvalidArgError("adena");
}
namespace {
TEST(ErrorOr, UserErrorData) {
ErrorOr<int> a = t4();
EXPECT_EQ(errc::invalid_argument, a);
EXPECT_EQ("adena", t4().getError<InvalidArgError>().ArgName);
}
} // end anon namespace