Two error handling mechanisms are provided in Aidge: std::expected
and AIDGE_*
macros. The two are complementary:
-
std::expected
should be used in backend code, that may run on an embeded targets. This is the case notably for core graph structure, operators implementation... -
AIDGE_*
macros may be used in front-end code, that will not run on an embeded targets.
std::expected
Backend/export code: use The primary error handling mechanism in Aidge should be std::expected
with error type std::error_code
. Although it is only available in C++23, a standard-compliant C++11 version is included in utils/future_std/expected.hpp
under the namespace future_std
.
This is the pure STL-based C++ standard compliant version of error handling mechanism recommanded in ASIL-certified systems AUTOSAR(https://www.autosar.org/fileadmin/standards/R22-11/AP/AUTOSAR_EXP_AdaptivePlatformInterfacesGuidelines.pdf).
Example:
template <typename R>
future_std::expected<R, std::error_code> MyClass::getAttr(const char* name) const {
for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
if (strcmp(EnumStrings<ATTRS_ENUM>::data[i], name) == 0) {
return getAttr<R>(i);
}
}
return future_std::unexpected(std::error_code(MyClassErrCode::NotFound));
}
Usage example:
MyClass a;
auto res = a.getAttr<int>("test");
// Check there is a valid value:
if (res)
... = *res;
// Use value or default value if error:
... = res.value_or(0);
Chaining & propagation
std::expected
should be propagated along the whole call graph like this:
future_std::expected<AnyType, std::error_code> func3() {
auto res1 = func1();
if (!res1)
// res1 value type might be different from func3 return value type!
return future_std::unexpected(res1.error());
auto res2 = func2(*res1);
if (!res2)
return future_std::unexpected(res2.error());
auto res3 = func3(*res2);
// If res3 value type is the same as func3 return value type, return the result directly
return res3;
}
...
future_std::unexpected
instead and propagate the error to the upper stage. Use future_std::unexpected<void, std::error_code>
for functions with no return value.
Error code implementation
In order to be able to propagate any type of error, with take advantage of std::error_code
. It acts like an abstract, polymorphic enum, for std::expected
.
The error code mechanism itself should be implemented on a per-class basis. Each class should provide the following:
// FILE: MyClass.hpp
# include <system_error>
enum class MyClassErrCode {
// no 0
NotFound = 1, // non-existant attribute
WrongType, // wrong attribute type
};
namespace std {
template <>
struct is_error_code_enum<MyClassErrCode> : true_type {};
}
std::error_code make_error_code(MyClassErrCode);
// FILE: MyClass.cpp
namespace { // anonymous namespace
struct MyClassErrCategory : std::error_category {
const char* name() const noexcept override;
std::string message(int ev) const override;
};
const char* MyClassErrCategory::name() const noexcept {
return "MyClass";
}
std::string MyClassErrCategory::message(int ev) const {
switch (static_cast<MyClassErrCode>(ev)) {
case MyClassErrCode::NotFound:
return "attribute not found";
case MyClassErrCode::WrongType:
return "wrong attribute type";
default:
return "(unrecognized error)";
}
}
const MyClassErrCategory theMyClassErrCategory {};
} // anonymous namespace
std::error_code make_error_code(MyClassErrCode e) {
return {static_cast<int>(e), theMyClassErrCategory};
}
AIDGE_*
macros
Front-end code: use For (rare) cases when std::expected
is not usable, for example for reference return values, the error handling macros in utils/ErrorHandling.hpp
should be used.
Example:
template <typename R>
R& getAttr(const char* name) {
for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
if (strcmp(EnumStrings<ATTRS_ENUM>::data[i], name) == 0) {
return getAttr<R>(i);
}
}
AIDGE_THROW_OR_ABORT(std::runtime_error, "attribute \"%s\" not found", name);
}
AIDGE_THROW_OR_ABORT
instead. No exception catching is allowed.
You can use the AIDGE_INTERNAL_ASSERT
macro for internal assertions (not directly related to user's inputs) that may be useful for debug.
Functions using this mechanism should be avoided in ASIL-certified systems.