-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
Argum can operate in 3 modes selected at compile time:
- Using exceptions (this is the default). In this mode parsing errors produce exceptions.
- Using expected values, with exceptions enabled. In this mode parsing errors are reported via
Expected<T>
return values. This class similar to proposedstd::expected
orboost::outcome
. Trying to access aresult.value()
when it contains an error throws an equivalent exceptions. This mode is enabled viaARGUM_USE_EXPECTED
macro. - With exceptions disabled. In this mode expected values used as above but trying to access a
result.value()
when it contains an error callsstd::terminate
. This mode can be manually enabled viaARGUM_NO_THROW
macro. On Clang, GCC and MSVC Argum automatically detects if exceptions are disabled during compilation and switches to this mode.
Note that these modes only affect handling of parsing errors. Logic errors such as passing incorrect parameters to configure parser always assert
in debug and std::terminate
in non-debug builds.
Also note that, unless you are building with exceptions disabled, various standard library facilities may throw exceptions (e.g. std::bad_alloc
) that will propagate out of Parser
methods.
Whether you use exceptions or expected values Argum's parser provides the following safety guarantees:
- Non-const methods of parser provide basic safety guarantee. If an exception is thrown/error returned the only safe thing to do is to destroy the parser object. (There is no
clear
orreset
method to bring it to a known state). - Const methods, notably
parse
, provide strong safety guarantee. If an exception is thrown the state of the parser is unchanged
The same classes are thrown as exceptions in exception mode and as errors in expected values one. All of the derive from either ParsingException
or WParsingException
base classes, which themselves derive from std::exception
.
The base classes provide message()
member function that returns an std::string_view
or std::wstring_view
respectively containing the error message.
If you ever need to differentiate between different error types you have multiple options:
-
catch
-ing them in exception throwing mode -
dynamic_cast
-ing them if you have RTTI enabled - Using provided
as<T>()
member method that performs a safe cast without RTTI. For exampleauto result = parse(...); if (auto error = result.error()) { //let's treat a specific error differently if (auto ambiguousOptionError = error ->as<Parser::AmbiguousOption>()) { //here ambiguousOptionError is Parser::AmbiguousOption * } }
The currently defined exception classes are:
[W]ParsingException
- base class
-
[W]ResponseFileReader::Exception
- issues opening or reading response file. Members:std::filesystem::path filename; std::error_code error;
-
[W]Parser::UnrecognizedOption
- an unrecognized option. Members:std::[w]string option;
-
[W]Parser::AmbiguousOption
- an option is ambiguous (e.g.--fo
when--foo
and--foobar
are defined). Members:std::[w]string option; std::vector<std::[w]string> possibilities;
-
[W]Parser::MissingOptionArgument
- required option argument is not provided. Members:std::[w]string option;
-
[W]Parser::ExtraOptionArgument
- option that has no arguments is given one (e.g.--no-arg-option=arg
). Members:std::[w]string option;
-
[W]Parser::ExtraPositional
- an unexpected positional argument has been provided. Members:std::[w]string value;
-
[W]Parser::ValidationError
- validation of an option or argument or overall constraints has failed. This class has no extra members.
Since there is currently no standard expected value type, Argum uses its own. It is called BasicExpected<Char, T>
where Char
argument specifies the character type for the errors it can hold and T
is the expected type. For convenience, as usual, there are two typedefs: Expected<T>
and WExpected<T>
that use char
and wchar_t
respectively. In what follows we will use Expected
as a shorthand for either of these.
An instance of Expected<T>
contains either a T
value or an error - a [shared] pointer to a basic class ParsingException
. These can be obtained and manipulated via its methods. Note that it is possible to have Expected<void>
. In fact this is exactly what regular parse()
method returns. In this case the stored "value" is nothing but the relevant methods returning it are still there (for generic code) - they just return void
.
Here is how to use Expected
:
-
Canonical check for error and use expected value otherwise:
auto result = someFunction(...); if (auto error = result.error()) { //.error() returns std::shared_ptr<ParsingException> //Error path, you can: //propagate Expected return error; //or log and return cout << error->message(); return; ... //etc. } else { //Success path. Here we can use the expected value if desired //operator* doesn't perform any checks. It is safe to use here because the error check in the if auto value = *result; }
-
Boolean checks
auto result = someFunction(...); if (result) { //result contains value } if (!result) { //result contain error }
Note that unless you don't care about the actual error, it is more efficient to call
error()
and compare the result tonullptr
than perform a boolean check and callerror()
anyway. -
Accessing value
auto result = someFunction(...); //Safe: // - will throw contained error if the value is not present (if exceptions are enabled) or abort (if not) // - not efficient if you already know the value is there auto value = result.value(); //Unsafe: // - does not check if the value is there so the behavior is undefined if it isn't // - efficient if you know the value is there auto value = *result; //Unsafe member access //Operator -> is just like * but returns a pointer. Also unsafe without a prior check. //Only defined if T is not void result->memberFunction();
-
Constructing
//default construct default constructs the value Expected<std::string> expected; assert(*expected == ""); //from value Expected<int> expected = 7; Expected<int> expected(7); //or {7} //calling value constructor in-place Expected<std::string> expected(5, 'c'); assert(*expected == "ccccc"); //from shared pointer to error catch(ParsingException & ex) { //clone() method returns a shared pointer to cloned exception value Expected<int> expected(std::move(ex).clone()); } //with error constructed in place //this constructs expected holding Parser::ValidationError with a message "invalid argument" //Failure<type of error to store> marks this as an "error" constructor Expected<int> expected(Failure<Parser::ValidationError>, "invalid argument");
-
Conversions
AnyExpected<T>
can be converted intoExpected<AnotherT>
ifT
is convertible toAnotherT
. If an expected stores an error then the error is carried over. If it stores a value then the value is converted.Expected<int> expectedInt = someFunction(); Expected<long> expectedLong = expectedInt;
As a convenience any
Expected<T>
can be converted toExpected<void>
. This simply discards the value if no error or carries over the error.Expected<void> foo() { Expected<std::string> result = someFunc(); if (!result) return result; //this works due to conversion to Expected<void> //use *result }
- Home
-
Usage Guide
Basics
Error Handling
Thread Safety
Adding Help
Defining options
Positional arguments
Reporting errors from handlers
Parsing common data types
Validation
Depending on order
Subcommands
Response files
Partial Parsing -
Advanced
Syntax Description
Customizing syntax
Customizing usage and help
Localization