Skip to content

Instantly share code, notes, and snippets.

@Broxzier
Created January 26, 2023 11:31
Show Gist options
  • Save Broxzier/35ef7f82988e8d7af962088a1bcdabc4 to your computer and use it in GitHub Desktop.
Save Broxzier/35ef7f82988e8d7af962088a1bcdabc4 to your computer and use it in GitHub Desktop.
Using std::excepted to ensure all exceptions are caught
#include <exception>
#include <expected>
#include <iostream>
#include <variant>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
namespace IO
{
struct FileNotFound {};
struct FileInUse {};
enum class FileError {
FileNotFound,
FileInUse,
};
struct FileHandle {};
using ExpectedExceptions = std::variant<FileNotFound, FileInUse>;
FileHandle OpenFileForWriting1()
{
// Just some volatile data to avoid optimization
static volatile int w = 0;
if (w == 0)
throw FileNotFound{};
if (w == 1)
throw FileInUse{};
return FileHandle{};
}
std::expected<FileHandle, FileError> OpenFileForWriting2()
{
// Just some volatile data to avoid optimization
static volatile int w = 0;
if (w == 0)
return std::unexpected{ FileError::FileNotFound };
if (w == 1)
return std::unexpected{ FileError::FileInUse };
return FileHandle{};
}
std::expected<FileHandle, ExpectedExceptions> OpenFileForWriting3()
{
// Just some volatile data to avoid optimization
static volatile int w = 0;
if (w == 0)
return std::unexpected{ IO::FileNotFound{} };
if (w == 1)
return std::unexpected{ IO::FileInUse{} };
return FileHandle{};
}
}
int main()
{
// Typical C++ exception handling with try/catch
// Pros: Expected and unexpected code handling is separated
// Cons: No way to ensure all possible exceptions are handled
try {
auto handle1 = IO::OpenFileForWriting1();
// Use file handle
}
catch (const IO::FileNotFound& ex) { std::cerr << "IO::FileNotFound was thrown\n"; }
catch (const IO::FileInUse& ex) { std::cerr << "IO::FileInUse was thrown\n"; }
catch (...) { std::cerr << "Unknown exception.\n"; }
// Error handling with std::expected and an enum
// Pros: Expected and unexpected code handling is separated
// Cons: Requires compiled settings to ensure all exceptions are caught
auto handle2 = IO::OpenFileForWriting2();
if (handle2) {
// Use file handle
} else {
switch (handle2.error())
{
case IO::FileError::FileNotFound: std::cerr << "IO::FileError::FileNotFound was returned\n"; break;
case IO::FileError::FileInUse: std::cerr << "IO::FileError::FileInUse was returned\n"; break;
default: std::cerr << "Unknown error returned\n"; break;
}
}
// Error handling with std::expected and a std::variant
// Pros: Expected and unexpected code handling is separated; ensures all exceptions are caught
// Cons: Looks more complicated than try/catch; requires a handler for each type
auto handle3 = IO::OpenFileForWriting3();
if (handle3) {
// Use file handle
} else {
auto error = handle3.error();
std::visit(overloaded {
[](const IO::FileNotFound& error){ std::cerr << "IO::FileNotFound was returned\n"; },
[](const IO::FileInUse& error){ std::cerr << "IO::FileInUse was returned\n"; },
// No need for catching unknown errors, because std::visit ensured we already catch each one
}, error);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment