Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions error/v2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
cmake_minimum_required(VERSION 3.18)

project(error LANGUAGES CXX)

# Import third-party dependencies.
find_package(GTest REQUIRED CONFIG)
find_package(tl-expected REQUIRED CONFIG)

# Enable some compiler warnings (supported by gcc & clang).
set(warnings
-Wall
-Wconversion
-Werror
-Wextra
-Wformat=2
-Wold-style-cast
-Woverloaded-virtual
-Wshadow
-Wsign-conversion
-Wuninitialized
-Wunused
)
string(REPLACE ";" " " warnings "${warnings}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${warnings}")

add_library(error SHARED)
add_library(caesar::error ALIAS error)

# Require C++17.
target_compile_features(error PUBLIC cxx_std_17)

# Add sources.
set(sources
caesar/error/domain_error.cpp
caesar/error/error_code.cpp
caesar/error/error.cpp
caesar/error/expected.cpp
caesar/error/out_of_range.cpp
)
target_sources(error PRIVATE ${sources})

# Add include dirs.
target_include_directories(
error
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)

# Link to imported targets.
target_link_libraries(
error
PUBLIC
tl::expected
)

# Add test sources.
set(tests
test/error_code_test.cpp
test/error_test.cpp
test/expected_test.cpp
)

add_executable(error-test ${tests})

target_link_libraries(
error-test
PRIVATE
caesar::error
GTest::gmock_main
)

# Register tests with CTest.
enable_testing()
include(GoogleTest)
gtest_discover_tests(error-test)
7 changes: 7 additions & 0 deletions error/v2/caesar/error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include "error/domain_error.hpp"
#include "error/error_code.hpp"
#include "error/error.hpp"
#include "error/expected.hpp"
#include "error/out_of_range.hpp"
17 changes: 17 additions & 0 deletions error/v2/caesar/error/domain_error.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "domain_error.hpp"

namespace caesar {

const char* get_error_category(DomainError) noexcept { return "DomainError"; }

const char*
get_error_string(DomainError e) noexcept
{
switch (e) {
case DomainError::DivisionByZero: return "Division by zero";
}

return "<unknown>";
}

} // namespace caesar
23 changes: 23 additions & 0 deletions error/v2/caesar/error/domain_error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

namespace caesar {

/**
* Error code used to indicate domain errors, i.e. situations where the inputs
* are outside of the domain on which an operation is defined.
*
* \see ErrorCode
*/
enum class DomainError {
DivisionByZero = 1,
};

/** \private implements ErrorCode::category() */
const char*
get_error_category(DomainError e) noexcept;

/** \private implements ErrorCode::description() */
const char*
get_error_string(DomainError e) noexcept;

} // namespace caesar
1 change: 1 addition & 0 deletions error/v2/caesar/error/error.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "error.hpp"
105 changes: 105 additions & 0 deletions error/v2/caesar/error/error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include "error_code.hpp"

#include <experimental/source_location>
#include <type_traits>

namespace caesar {

/**
* Describes an error encountered during processing
*
* The Error class stores an error code along with contextual information about
* where the error originated from in the source code (filename and line
* number). When constructed from an ErrorCode (or any supported error code
* enumeration type), by default, the source location information is populated
* based on the call site where the Error object was constructed.
*
* \see ErrorCode
* \see Expected
*/
class Error {
using source_location = std::experimental::source_location;

public:
/**
* Construct a new Error object.
*
* \param[in] error_code error code
* \param[in] file source code filename where the error occurred
* \param[in] line source code line number where the error occurred
*/
constexpr Error(const ErrorCode& error_code,
const char* file,
int line) noexcept
: error_code_(error_code), file_(file), line_(line)
{}

/**
* Construct a new Error object.
*
* The source location defaults to the call site where the Error object was
* constructed.
*
* \param[in] error_code error code
* \param[in] origin source code location where the error occurred
*/
constexpr Error(
const ErrorCode& error_code,
const source_location& origin = source_location::current()) noexcept
: Error(error_code, origin.file_name(), static_cast<int>(origin.line()))
{}

/**
* Construct a new Error object.
*
* The source location defaults to the call site where the Error object was
* constructed.
*
* This overload participates in overload resolution only if
* `std::is_constructible_v<ErrorCode, ErrorCodeEnum> == true`.
*
* \tparam ErrorCodeEnum
* an error code enumeration type supported by ErrorCode
*
* \param[in] e error code enumeration object
* \param[in] origin source code location where the error occurred
*/
template<class ErrorCodeEnum,
typename = std::enable_if_t<
std::is_constructible_v<ErrorCode, ErrorCodeEnum>>>
constexpr Error(
ErrorCodeEnum e,
const source_location& origin = source_location::current()) noexcept
: Error(ErrorCode(e), origin)
{}

/** Return the error code. */
constexpr const ErrorCode&
error_code() const noexcept
{
return error_code_;
}

/** Return the source code filename where the error occurred. */
constexpr const char*
file() const noexcept
{
return file_;
}

/** Return the source code line number where the error occurred. */
constexpr int
line() const noexcept
{
return line_;
}

private:
ErrorCode error_code_;
const char* file_;
int line_;
};

} // namespace caesar
1 change: 1 addition & 0 deletions error/v2/caesar/error/error_code.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "error_code.hpp"
100 changes: 100 additions & 0 deletions error/v2/caesar/error/error_code.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#pragma once

#include "domain_error.hpp"
#include "out_of_range.hpp"

#include <variant>

namespace caesar {

/**
* A type-erased error code
*
* ErrorCode facilitates interoperability between any number of domain-specific
* error codes that an application and its third-party dependencies may
* need to support. It does so by providing a common type that can be used to
* represent error code enumerations of different types.
*
* Unlike
* [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code),
* ErrorCode uses a variant/visitor idiom rather than dynamic polymorphism to
* implement type erasure. This makes extending ErrorCode more intrusive (you
* need to actually modify the class definition in order to support additional
* error code enum types). However, ErrorCode boasts a number of beneficial
* properties compared to `std::error_code`:
*
* - ErrorCode objects can be constructed in device code and can be safely
* passed between the host and device
* - Supporting custom error codes requires significantly less boilerplate
* - ErrorCode is a *LiteralType* and most operations are `constexpr`
*
* ### Example
*
* ```c++
* ErrorCode ec = DomainError::DivisionByZero;
*
* std::cout << ec.category() << "\n"; // "DomainError"
* std::cout << ec.description() << "\n"; // "Division by zero"
* ```
*
* \see Error
*/
class ErrorCode : private std::variant<DomainError, OutOfRange> {
using Base = std::variant<DomainError, OutOfRange>;

public:
/** Integral type that can be used to represent error code values */
using value_type = int;

using Base::Base;
using Base::operator=;

ErrorCode() = delete;

/** Return the error code value. */
constexpr value_type
value() const noexcept
{
return std::visit([](auto arg) { return static_cast<value_type>(arg); },
base());
}

/** Return the associated error category name. */
const char*
category() const noexcept
{
return std::visit([](auto arg) { return get_error_category(arg); },
base());
}

/** Return a message describing the error code. */
const char*
description() const noexcept
{
return std::visit([](auto arg) { return get_error_string(arg); },
base());
}

/** Compare two ErrorCode objects */
friend constexpr bool
operator==(const ErrorCode& lhs, const ErrorCode& rhs) noexcept
{
return lhs.base() == rhs.base();
}

/** \copydoc operator==(const ErrorCode&, const ErrorCode&) */
friend constexpr bool
operator!=(const ErrorCode& lhs, const ErrorCode& rhs) noexcept
{
return not(lhs == rhs);
}

private:
constexpr const Base&
base() const noexcept
{
return static_cast<const Base&>(*this);
}
};

} // namespace caesar
1 change: 1 addition & 0 deletions error/v2/caesar/error/expected.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "expected.hpp"
Loading