Skip to content
Merged
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
3 changes: 3 additions & 0 deletions cpp.hint
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@

#define IRIS_FORCEINLINE
#define IRIS_LIFETIMEBOUND

#define IRIS_THROW_NORETURN [[noreturn]]

86 changes: 86 additions & 0 deletions include/iris/exception.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#ifndef IRIS_EXCEPTION_HPP
#define IRIS_EXCEPTION_HPP

#include <iris/string.hpp>

#include <exception>
#include <format>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>

#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<class T, class... Args>
concept constructible_from_string_like_types =
std::is_constructible_v<T, Args..., std::string> ||
std::is_constructible_v<T, Args..., std::string_view> ||
std::is_constructible_v<T, Args..., const char*>;

} // detail

inline namespace error_functions {

template<class E>
IRIS_THROW_NORETURN void throwf()
{
static_assert(std::is_base_of_v<std::exception, E>);
static_assert(std::is_constructible_v<E>);
IRIS_THROW_IMPL(E{});
}

template<class E, class Arg, class... Rest>
requires std::is_constructible_v<E, Arg, Rest...>
IRIS_THROW_NORETURN void throwf(Arg&& arg, Rest&&... rest)
{
static_assert(std::is_base_of_v<std::exception, E>);
static_assert(!std::is_base_of_v<std::exception, std::remove_cvref_t<Arg>>, "don't copy/move construct exception types directly");
IRIS_THROW_IMPL(E{std::forward<Arg>(arg), std::forward<Rest>(rest)...});
}

template<class E, class... Args>
requires detail::constructible_from_string_like_types<E>
IRIS_THROW_NORETURN void throwf(std::format_string<Args...> fmt, Args&&... args)
{
static_assert(std::is_base_of_v<std::exception, E>);
IRIS_THROW_IMPL(E{std::format(std::move(fmt), std::forward<Args>(args)...)});
}

template<class E, NotStringLike Arg0, class... Args>
requires detail::constructible_from_string_like_types<E, Arg0>
IRIS_THROW_NORETURN void throwf(Arg0&& arg0, std::format_string<Args...> fmt, Args&&... args)
{
static_assert(std::is_base_of_v<std::exception, E>);
IRIS_THROW_IMPL(E{std::forward<Arg0>(arg0), std::format(std::move(fmt), std::forward<Args>(args)...)});
}

template<class E, NotStringLike Arg0, NotStringLike Arg1, class... Args>
requires detail::constructible_from_string_like_types<E, Arg0, Arg1>
IRIS_THROW_NORETURN void throwf(Arg0&& arg0, Arg1&& arg1, std::format_string<Args...> fmt, Args&&... args)
{
static_assert(std::is_base_of_v<std::exception, E>);
IRIS_THROW_IMPL(
E{
std::forward<Arg0>(arg0), std::forward<Arg1>(arg1),
std::format(std::move(fmt), std::forward<Args>(args)...)
}
);
}

} // error_functions

} // iris

#endif
3 changes: 3 additions & 0 deletions include/iris/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ concept StringLike = requires(T t) {
std::basic_string_view{t};
};

template<class T>
concept NotStringLike = !StringLike<T>;

} // iris

#endif
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
70 changes: 70 additions & 0 deletions test/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <iris/requirements.hpp>
#include <iris/compare.hpp>
#include <iris/fixed_string.hpp>
#include <iris/exception.hpp>

#include <concepts>
#include <utility>
Expand Down Expand Up @@ -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<E>(__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::system_error>(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<std::system_error>(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