diff --git a/cpp.hint b/cpp.hint index 457541a..238b569 100644 --- a/cpp.hint +++ b/cpp.hint @@ -17,3 +17,6 @@ #define IRIS_FORCEINLINE #define IRIS_LIFETIMEBOUND + +#define IRIS_THROW_NORETURN [[noreturn]] + diff --git a/include/iris/exception.hpp b/include/iris/exception.hpp new file mode 100644 index 0000000..100e269 --- /dev/null +++ b/include/iris/exception.hpp @@ -0,0 +1,86 @@ +#ifndef IRIS_EXCEPTION_HPP +#define IRIS_EXCEPTION_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef IRIS_THROW_IMPL +# define IRIS_THROW_IMPL(...) throw __VA_ARGS__ +#endif + +#ifndef IRIS_THROW_NORETURN +# define IRIS_THROW_NORETURN [[noreturn]] +#endif + +namespace iris { + +namespace detail { + +template +concept constructible_from_string_like_types = + std::is_constructible_v || + std::is_constructible_v || + std::is_constructible_v; + +} // detail + +inline namespace error_functions { + +template +IRIS_THROW_NORETURN void throwf() +{ + static_assert(std::is_base_of_v); + static_assert(std::is_constructible_v); + IRIS_THROW_IMPL(E{}); +} + +template + requires std::is_constructible_v +IRIS_THROW_NORETURN void throwf(Arg&& arg, Rest&&... rest) +{ + static_assert(std::is_base_of_v); + static_assert(!std::is_base_of_v>, "don't copy/move construct exception types directly"); + IRIS_THROW_IMPL(E{std::forward(arg), std::forward(rest)...}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_THROW_NORETURN void throwf(std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_THROW_IMPL(E{std::format(std::move(fmt), std::forward(args)...)}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_THROW_NORETURN void throwf(Arg0&& arg0, std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_THROW_IMPL(E{std::forward(arg0), std::format(std::move(fmt), std::forward(args)...)}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_THROW_NORETURN void throwf(Arg0&& arg0, Arg1&& arg1, std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_THROW_IMPL( + E{ + std::forward(arg0), std::forward(arg1), + std::format(std::move(fmt), std::forward(args)...) + } + ); +} + +} // error_functions + +} // iris + +#endif diff --git a/include/iris/string.hpp b/include/iris/string.hpp index 131432e..a0348d6 100644 --- a/include/iris/string.hpp +++ b/include/iris/string.hpp @@ -13,6 +13,9 @@ concept StringLike = requires(T t) { std::basic_string_view{t}; }; +template +concept NotStringLike = !StringLike; + } // iris #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index da39afb..de362f5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -150,7 +150,7 @@ endfunction() # Iris tests if(PROJECT_IS_TOP_LEVEL) - if(IRIS_CI_COMPONENT STREQUAL iris) + if(NOT DEFINED IRIS_CI_COMPONENT OR IRIS_CI_COMPONENT STREQUAL iris) add_subdirectory(rvariant) set( diff --git a/test/core.cpp b/test/core.cpp index f7a7a64..a60b550 100644 --- a/test/core.cpp +++ b/test/core.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -452,4 +453,73 @@ TEST_CASE("fixed_string") CHECK((std::ranges::equal(str, "foobar"sv))); } + +#define IRIS_TEST_THROWF(expected, E, ...) \ + CHECK_THROWS_AS( \ + ([]{ \ + try { \ + iris::throwf(__VA_ARGS__); \ + } catch (std::exception const& e) { \ + CHECK(e.what() == std::string_view{expected}); \ + throw; \ + } \ + })(), \ + E \ + ) + +TEST_CASE("throwf") +{ + class my_exception : public std::runtime_error + { + public: + my_exception(std::string const& name, std::string const& message) + : runtime_error(name + ": " + message) + {} + }; + + IRIS_TEST_THROWF("foo", std::runtime_error, "foo"); + IRIS_TEST_THROWF("foo", std::runtime_error, std::string{"foo"}); + IRIS_TEST_THROWF("foo", std::runtime_error, std::string{"foo"}.c_str()); + IRIS_TEST_THROWF("foo", std::runtime_error, std::string_view{"foo"}); + IRIS_TEST_THROWF("{}", std::runtime_error, "{}"); + IRIS_TEST_THROWF("42", std::runtime_error, "{}", 42); + IRIS_TEST_THROWF("foo", std::runtime_error, "{}", "foo"); + IRIS_TEST_THROWF("foo", std::runtime_error, "{}", std::string{"foo"}); + IRIS_TEST_THROWF("foo", std::runtime_error, "{}", std::string{"foo"}.c_str()); + IRIS_TEST_THROWF("foo", std::runtime_error, "{}", std::string_view{"foo"}); + + IRIS_TEST_THROWF("{}: bar", my_exception, "{}", "bar"); + IRIS_TEST_THROWF("foo: bar", my_exception, "foo", "bar"); + + // constructible with argument + IRIS_TEST_THROWF("foobar", std::runtime_error, "foobar"); + + // constructible with format string + IRIS_TEST_THROWF("33 - 4", std::runtime_error, "{} - {}", 33, 4); + + CHECK_THROWS_AS( + ([]{ + try { + iris::throwf(std::make_error_code(std::errc::invalid_argument), "{}", 42); + } catch (std::system_error const& e) { + CHECK(e.code() == std::make_error_code(std::errc::invalid_argument)); + throw; + } + })(), + std::system_error + ); + + CHECK_THROWS_AS( + ([]{ + try { + iris::throwf(33 - 4, std::generic_category(), "{}", 42); + } catch (std::system_error const& e) { + CHECK((e.code() == std::error_code{33 - 4, std::generic_category()})); + throw; + } + })(), + std::system_error + ); +} + } // unit_test