aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@gmail.com>2025-02-26 11:24:59 +0100
committerFelix Morgner <felix.morgner@gmail.com>2025-02-26 11:24:59 +0100
commit440d47cae6431de3332ac934b6056a970cc1a0d7 (patch)
treeaae63811972647f6ffe8a13d440171cfc8752860 /tests
parent124d4f363a9d86b023aadec0eb0a3eb6fc1cbfdd (diff)
downloadnewtype-440d47cae6431de3332ac934b6056a970cc1a0d7.tar.xz
newtype-440d47cae6431de3332ac934b6056a970cc1a0d7.zip
build: remove conan
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt33
-rw-r--r--tests/src/arithmetic.cpp297
-rw-r--r--tests/src/constructors.cpp102
-rw-r--r--tests/src/conversion.cpp133
-rw-r--r--tests/src/derivation_clause.cpp63
-rw-r--r--tests/src/equality_comparison.cpp102
-rw-r--r--tests/src/hash.cpp59
-rw-r--r--tests/src/io_operators.cpp110
-rw-r--r--tests/src/iterable.cpp1060
-rw-r--r--tests/src/relational_operators.cpp249
-rw-r--r--tests/src/threeway_comparison.cpp52
11 files changed, 2260 insertions, 0 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..47b8331
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,33 @@
+find_package("Catch2" "3.1"
+ COMPONENTS "Catch2WithMain"
+ REQUIRED
+)
+
+include("CTest")
+include("Catch")
+
+file(GLOB SOURCES
+ RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
+ CONFIGURE_DEPENDS
+ "src/*.cpp"
+)
+
+
+add_executable("${PROJECT_NAME}_tests"
+ ${SOURCES}
+)
+
+target_link_libraries("${PROJECT_NAME}_tests"
+ "${PROJECT_NAME}::${PROJECT_NAME}"
+ "Catch2::Catch2WithMain"
+)
+
+target_compile_options("${PROJECT_NAME}_tests" PRIVATE
+ "$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>"
+ "$<$<CXX_COMPILER_ID:GNU,Clang>:-Wextra>"
+ "$<$<CXX_COMPILER_ID:GNU,Clang>:-Werror>"
+ "$<$<CXX_COMPILER_ID:GNU,Clang>:-pedantic-errors>"
+ "$<$<CXX_COMPILER_ID:GNU>:-fconcepts-diagnostics-depth=5>"
+)
+
+catch_discover_tests("${PROJECT_NAME}_tests")
diff --git a/tests/src/arithmetic.cpp b/tests/src/arithmetic.cpp
new file mode 100644
index 0000000..30c243f
--- /dev/null
+++ b/tests/src/arithmetic.cpp
@@ -0,0 +1,297 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <type_traits>
+
+SCENARIO("Addition", "[arithmetic]")
+{
+ struct addable_type
+ {
+ auto constexpr operator+(addable_type const &) const -> addable_type
+ {
+ return {};
+ };
+ };
+
+ GIVEN("A new_type instance not deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+
+ THEN("it is not addable")
+ {
+ STATIC_REQUIRE(!nt::concepts::addable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is addable")
+ {
+ STATIC_REQUIRE(nt::concepts::addable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a non-addable class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<void *, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is not addable")
+ {
+ STATIC_REQUIRE(!nt::concepts::addable<addable_type> == nt::concepts::addable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over an addable class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<addable_type, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is addable")
+ {
+ STATIC_REQUIRE(nt::concepts::addable<addable_type> == nt::concepts::addable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("addition produces the same type")
+ {
+ STATIC_REQUIRE(std::is_same_v<type_alias, decltype(std::declval<type_alias const &>() + std::declval<type_alias const &>())>);
+ }
+ }
+
+ GIVEN("Two objects of a new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+ auto lhs = type_alias{24};
+ auto rhs = type_alias{18};
+
+ THEN("addition produces the correct result with respect to the base type")
+ {
+ REQUIRE((lhs + rhs).decay() == 24 + 18);
+ }
+ }
+}
+
+SCENARIO("Subtraction", "[arithmetic]")
+{
+ struct subtractable_type
+ {
+ auto constexpr operator-(subtractable_type const &) const -> subtractable_type
+ {
+ return {};
+ };
+ };
+
+ GIVEN("A new_type not deriving nt::arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+
+ THEN("it is not subtractable")
+ {
+ STATIC_REQUIRE(!nt::concepts::subtractable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is subtractable")
+ {
+ STATIC_REQUIRE(nt::concepts::subtractable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a non-subtractable class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<void *, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is not addable")
+ {
+ STATIC_REQUIRE(!nt::concepts::subtractable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a subtractable class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<subtractable_type, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is subtractable")
+ {
+ STATIC_REQUIRE(nt::concepts::subtractable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("subtraction produces the same type")
+ {
+ STATIC_REQUIRE(std::is_same_v<type_alias, decltype(std::declval<type_alias const &>() - std::declval<type_alias const &>())>);
+ }
+ }
+
+ GIVEN("Two objects of a new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+ auto lhs = type_alias{24};
+ auto rhs = type_alias{18};
+
+ THEN("subtraction produces the correct result with respect to the base type")
+ {
+ REQUIRE((lhs - rhs).decay() == 24 - 18);
+ }
+ }
+}
+
+SCENARIO("Multiplication", "[arithmetic]")
+{
+ struct multipliable_type
+ {
+ auto constexpr operator*(multipliable_type const &) const -> multipliable_type
+ {
+ return {};
+ };
+ };
+
+ GIVEN("A new_type not deriving nt::arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+
+ THEN("it is not multipliable")
+ {
+ STATIC_REQUIRE(!nt::concepts::multipliable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is multipliable")
+ {
+ STATIC_REQUIRE(nt::concepts::multipliable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a non-multipliable class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<void *, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is not multipliable")
+ {
+ STATIC_REQUIRE(!nt::concepts::multipliable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a multipliable class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<multipliable_type, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is multipliable")
+ {
+ STATIC_REQUIRE(nt::concepts::multipliable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("multiplication produces the same type")
+ {
+ STATIC_REQUIRE(std::is_same_v<type_alias, decltype(std::declval<type_alias const &>() * std::declval<type_alias const &>())>);
+ }
+ }
+
+ GIVEN("Two objects of a new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+ auto lhs = type_alias{24};
+ auto rhs = type_alias{18};
+
+ THEN("multiplication produces the correct result with respect to the base type")
+ {
+ REQUIRE((lhs * rhs).decay() == 24 * 18);
+ }
+ }
+}
+
+SCENARIO("Division", "[arithmetic]")
+{
+ struct dividable_type
+ {
+ auto constexpr operator/(dividable_type const &) const -> dividable_type
+ {
+ return {};
+ };
+ };
+
+ GIVEN("A new_type not deriving nt::arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+
+ THEN("it is not divisible")
+ {
+ STATIC_REQUIRE(!nt::concepts::divisible<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is divisible")
+ {
+ STATIC_REQUIRE(nt::concepts::divisible<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a non-divisible class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<void *, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is not divisible")
+ {
+ STATIC_REQUIRE(!nt::concepts::divisible<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a divisible class type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<dividable_type, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("it is divisible")
+ {
+ STATIC_REQUIRE(nt::concepts::divisible<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+
+ THEN("division produces the same type")
+ {
+ STATIC_REQUIRE(std::is_same_v<type_alias, decltype(std::declval<type_alias const &>() / std::declval<type_alias const &>())>);
+ }
+ }
+
+ GIVEN("Two objects of a new_type deriving nt::Arithmetic")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Arithmetic)>;
+ auto lhs = type_alias{30};
+ auto rhs = type_alias{15};
+
+ THEN("division produces the correct result with respect to the base type")
+ {
+ REQUIRE((lhs / rhs).decay() == 30 / 15);
+ }
+ }
+}
diff --git a/tests/src/constructors.cpp b/tests/src/constructors.cpp
new file mode 100644
index 0000000..b866f2e
--- /dev/null
+++ b/tests/src/constructors.cpp
@@ -0,0 +1,102 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <type_traits>
+
+using fundamental_types = std::tuple<bool,
+ char,
+ unsigned char,
+ signed char,
+ wchar_t,
+ char16_t,
+ char32_t,
+ short,
+ unsigned short,
+ int,
+ unsigned int,
+ long,
+ unsigned long,
+ long long,
+ unsigned long long,
+ float,
+ double,
+ long double>;
+
+TEMPLATE_LIST_TEST_CASE("Scenario: Construction from Fundamental Types", "[construction]", fundamental_types)
+{
+ GIVEN("A new_type over a fundamental type")
+ {
+ using type_alias = nt::new_type<TestType, struct tag>;
+
+ THEN("objects of it can be constructed from the fundamental type")
+ {
+ STATIC_REQUIRE(std::is_constructible_v<type_alias, TestType>);
+ }
+ }
+}
+
+SCENARIO("Default Construction", "[construction]")
+{
+ struct not_default_constructible
+ {
+ not_default_constructible() = delete;
+ };
+
+ GIVEN("A new_type over a default-constructible type")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+ static_assert(std::is_default_constructible_v<type_alias::base_type>);
+
+ THEN("it is default-constructible")
+ {
+ STATIC_REQUIRE(std::is_default_constructible_v<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a type that is not default-constructible")
+ {
+ using type_alias = nt::new_type<not_default_constructible, struct tag>;
+ static_assert(!std::is_default_constructible_v<type_alias::base_type>);
+
+ THEN("it is not default-constructible")
+ {
+ STATIC_REQUIRE_FALSE(std::is_default_constructible_v<type_alias>);
+ }
+ }
+}
+
+SCENARIO("Copy Construction", "[construction]")
+{
+ struct not_copy_constructible
+ {
+ not_copy_constructible() = default;
+ not_copy_constructible(not_copy_constructible const &) = delete;
+ not_copy_constructible(not_copy_constructible &&) = default;
+ auto operator=(not_copy_constructible const &) -> not_copy_constructible & = default;
+ auto operator=(not_copy_constructible &&) -> not_copy_constructible & = default;
+ };
+
+ GIVEN("A new_type over a copy-constructible type")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+ static_assert(std::is_copy_constructible_v<type_alias::base_type>);
+
+ THEN("it is copy-constructible")
+ {
+ STATIC_REQUIRE(std::is_copy_constructible_v<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a type that is not copy-constructible")
+ {
+ using type_alias = nt::new_type<not_copy_constructible, struct tag>;
+ static_assert(!std::is_copy_constructible_v<type_alias::base_type>);
+
+ THEN("it is not copy-constructible")
+ {
+ STATIC_REQUIRE_FALSE(std::is_copy_constructible_v<type_alias>);
+ }
+ }
+}
diff --git a/tests/src/conversion.cpp b/tests/src/conversion.cpp
new file mode 100644
index 0000000..e7ce51c
--- /dev/null
+++ b/tests/src/conversion.cpp
@@ -0,0 +1,133 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators_all.hpp>
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+using test_types = std::tuple<bool, char, int, double, std::string>;
+
+TEMPLATE_LIST_TEST_CASE("Scenario: Implicit Conversions", "[conversion]", test_types)
+{
+ GIVEN("A new_type not deriving nt::ImplicitConversion")
+ {
+ using type_alias = nt::new_type<TestType, struct conversion_test>;
+
+ THEN("it is not implicitly convertible to the base type")
+ {
+ STATIC_REQUIRE(!std::is_convertible_v<type_alias, TestType>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::ImplicitConversion")
+ {
+ using type_alias = nt::new_type<TestType, struct conversion_test, deriving(nt::ImplicitConversion)>;
+
+ THEN("it is implicitly convertible to the base type")
+ {
+ STATIC_REQUIRE(std::is_convertible_v<type_alias, TestType>);
+ }
+ }
+}
+
+TEMPLATE_LIST_TEST_CASE("Scenario: Decay", "[conversion]", test_types)
+{
+ GIVEN("Any new_type")
+ {
+ using type_alias = nt::new_type<TestType, struct conversion_test>;
+
+ THEN("it's decay() member function returns a value of the base type")
+ {
+ STATIC_REQUIRE(std::is_same_v<TestType, decltype(std::declval<type_alias>().decay())>);
+ }
+ }
+
+ GIVEN("Any new_type")
+ {
+ using type_alias = nt::new_type<TestType, struct conversion_test>;
+
+ WHEN("an object of that type is constructed")
+ {
+ auto integral_value = GENERATE(take(64, random(0, 127)));
+ auto value = [integral_value] {
+ if constexpr (std::is_same_v<std::string, TestType>)
+ {
+ return std::to_string(integral_value);
+ }
+ else
+ {
+ return static_cast<TestType>(integral_value);
+ }
+ }();
+ auto obj = type_alias{value};
+
+ THEN("it's decay() member function return the underlying value")
+ {
+ REQUIRE(obj.decay() == value);
+ }
+ }
+ }
+}
+
+SCENARIO("Nothrow Decay")
+{
+ struct strange_type
+ {
+ strange_type(strange_type const &) noexcept(false)
+ {
+ }
+ };
+
+ GIVEN("A new_type over a nothrow-copyable type")
+ {
+ using type_alias = nt::new_type<int, struct conversion_test>;
+
+ THEN("the decay member function is nothrow-invokable")
+ {
+ STATIC_REQUIRE(noexcept(std::declval<type_alias>().decay()));
+ }
+ }
+
+ GIVEN("A new_type over a non-nothrow-copyable type")
+ {
+ using type_alias = nt::new_type<strange_type, struct conversion_test>;
+
+ THEN("the decay member function is not nothrow-invokable")
+ {
+ STATIC_REQUIRE(!noexcept(std::declval<type_alias>().decay()));
+ }
+ }
+}
+
+SCENARIO("Nothrow Conversion")
+{
+ struct strange_type
+ {
+ strange_type(strange_type const &) noexcept(false)
+ {
+ }
+ };
+
+ GIVEN("A new_type over a nothrow-copy-constructible type")
+ {
+ using type_alias = nt::new_type<int, struct conversion_test>;
+
+ THEN("the decay member function is nothrow-invokable")
+ {
+ STATIC_REQUIRE(noexcept(std::declval<type_alias>().operator int()));
+ }
+ }
+
+ GIVEN("A new_type over a non-nothrow-copy-constructible type")
+ {
+ using type_alias = nt::new_type<strange_type, struct conversion_test>;
+
+ THEN("the decay member function is not nothrow-invokable")
+ {
+ STATIC_REQUIRE(!noexcept(std::declval<type_alias>().operator strange_type()));
+ }
+ }
+}
diff --git a/tests/src/derivation_clause.cpp b/tests/src/derivation_clause.cpp
new file mode 100644
index 0000000..78bd3d4
--- /dev/null
+++ b/tests/src/derivation_clause.cpp
@@ -0,0 +1,63 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <string>
+
+SCENARIO("Derivation Clause", "[infrastructure]")
+{
+ GIVEN("An empty derivation clause")
+ {
+ auto clause = nt::deriving();
+
+ THEN("it doesn't contain any derivable")
+ {
+ STATIC_REQUIRE_FALSE(nt::derives<decltype(clause), nt::Show>);
+ }
+ }
+
+ GIVEN("A derivation clause containing only nt::Show")
+ {
+ auto clause = deriving(nt::Show);
+
+ THEN("it doesn't contain nt::EqBase")
+ {
+ STATIC_REQUIRE_FALSE(nt::derives<decltype(clause), nt::EqBase>);
+ }
+
+ THEN("it contains nt::Show")
+ {
+ STATIC_REQUIRE(nt::derives<decltype(clause), nt::Show>);
+ }
+ }
+
+ GIVEN("A derivation clause containing only nt::Show and nt::EqBase")
+ {
+ auto clause = deriving(nt::Show, nt::EqBase);
+
+ THEN("it contains nt::EqBase")
+ {
+ STATIC_REQUIRE(nt::derives<decltype(clause), nt::EqBase>);
+ }
+
+ THEN("it contains nt::Show")
+ {
+ STATIC_REQUIRE(nt::derives<decltype(clause), nt::Show>);
+ }
+
+ THEN("it contains both nt::Show and nt::EqBase")
+ {
+ STATIC_REQUIRE(nt::derives<decltype(clause), nt::Show, nt::EqBase>);
+ }
+
+ THEN("it does not contain nt::Arithmetic")
+ {
+ STATIC_REQUIRE_FALSE(nt::derives<decltype(clause), nt::Arithmetic>);
+ }
+
+ THEN("it does not contain both nt::Arithmetic and nt::Show")
+ {
+ STATIC_REQUIRE_FALSE(nt::derives<decltype(clause), nt::Arithmetic, nt::Show>);
+ }
+ }
+}
diff --git a/tests/src/equality_comparison.cpp b/tests/src/equality_comparison.cpp
new file mode 100644
index 0000000..e0be7f9
--- /dev/null
+++ b/tests/src/equality_comparison.cpp
@@ -0,0 +1,102 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <string>
+#include <type_traits>
+#include <utility>
+
+SCENARIO("Equality Comparison", "[compare]")
+{
+ GIVEN("A new_type over an equality comparable type")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+
+ THEN("two objects of it with the same value compare equal")
+ {
+ REQUIRE(type_alias{42} == type_alias{42});
+ }
+
+ THEN("two objects of it with the same value do not compare not-equal")
+ {
+ REQUIRE_FALSE(type_alias{42} != type_alias{42});
+ }
+
+ THEN("two object of it with different values do not compare equal")
+ {
+ REQUIRE_FALSE(type_alias{42} == type_alias{43});
+ }
+
+ THEN("two object of it with different values compare not-equal")
+ {
+ REQUIRE(type_alias{42} != type_alias{43});
+ }
+
+ THEN("equality comparison returns bool")
+ {
+ STATIC_REQUIRE(std::is_same_v<bool, decltype(std::declval<type_alias>() == std::declval<type_alias>())>);
+ }
+
+ THEN("inequality comparison returns bool")
+ {
+ STATIC_REQUIRE(std::is_same_v<bool, decltype(std::declval<type_alias>() != std::declval<type_alias>())>);
+ }
+ }
+
+ GIVEN("A new_type deriving nt::EqBase")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::EqBase)>;
+
+ THEN("an instance of it compares equal to the equivalent base type value")
+ {
+ REQUIRE(type_alias{42} == 42);
+ }
+
+ THEN("an instance of it comapres not-equal to a different base type value")
+ {
+ REQUIRE(type_alias{42} != 43);
+ }
+ }
+
+ GIVEN("A new_type over a nothrow-comparable type")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+
+ static_assert(nt::concepts::nothrow_equality_comparable<type_alias::base_type>);
+ static_assert(nt::concepts::nothrow_inequality_comparable<type_alias::base_type>);
+
+ THEN("it is nothrow-equality-comparable")
+ {
+ STATIC_REQUIRE(nt::concepts::nothrow_equality_comparable<type_alias>);
+ }
+
+ THEN("it is nothrow-inequality-comparable")
+ {
+ STATIC_REQUIRE(nt::concepts::nothrow_inequality_comparable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a non-nothrow-comparable type")
+ {
+ struct not_nothrow_comparable
+ {
+ auto operator==(not_nothrow_comparable) const noexcept(false) -> bool;
+ auto operator!=(not_nothrow_comparable) const noexcept(false) -> bool;
+ };
+
+ using type_alias = nt::new_type<not_nothrow_comparable, struct tag>;
+
+ static_assert(!nt::concepts::nothrow_equality_comparable<type_alias::base_type>);
+ static_assert(!nt::concepts::nothrow_inequality_comparable<type_alias::base_type>);
+
+ THEN("it is not nothrow-equality-comparable")
+ {
+ STATIC_REQUIRE_FALSE(nt::concepts::nothrow_equality_comparable<type_alias>);
+ }
+
+ THEN("it is not nothrow-inequality-comparable")
+ {
+ STATIC_REQUIRE_FALSE(noexcept(std::declval<type_alias>() != std::declval<type_alias>()));
+ }
+ }
+}
diff --git a/tests/src/hash.cpp b/tests/src/hash.cpp
new file mode 100644
index 0000000..94f252f
--- /dev/null
+++ b/tests/src/hash.cpp
@@ -0,0 +1,59 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <string>
+#include <unordered_map>
+
+TEMPLATE_TEST_CASE("Hash", "[hash]", std::string, int)
+{
+ GIVEN("A new_type not deriving nt::Hash")
+ {
+ using type_alias = nt::new_type<TestType, struct tag>;
+ static_assert(nt::concepts::hashable<typename type_alias::base_type>);
+
+ THEN("it is not hashable")
+ {
+ STATIC_REQUIRE_FALSE(nt::concepts::hashable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a hashable type deriving nt::Hash")
+ {
+ using type_alias = nt::new_type<TestType, struct tag, deriving(nt::Hash)>;
+ static_assert(nt::concepts::hashable<typename type_alias::base_type>);
+
+ THEN("it is hashable")
+ {
+ STATIC_REQUIRE(nt::concepts::hashable<type_alias>);
+ }
+ }
+
+ GIVEN("A new_type over a non-hashable type deriving nt::Hash")
+ {
+ struct non_hashable
+ {
+ };
+ using type_alias = nt::new_type<non_hashable, struct tag, deriving(nt::Hash)>;
+ static_assert(!nt::concepts::hashable<typename type_alias::base_type>);
+
+ THEN("it is not hashable")
+ {
+ STATIC_REQUIRE_FALSE(nt::concepts::hashable<type_alias>);
+ }
+ }
+
+ GIVEN("A hashable new_type")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Hash)>;
+ static_assert(nt::concepts::hashable<typename type_alias::base_type>);
+
+ THEN("it can be used a the key in an unordered_map")
+ {
+ auto map = std::unordered_map<type_alias, int>{};
+ map[type_alias{42}] = 43;
+ REQUIRE(map[type_alias{42}] == 43);
+ }
+ }
+}
diff --git a/tests/src/io_operators.cpp b/tests/src/io_operators.cpp
new file mode 100644
index 0000000..f7f8f29
--- /dev/null
+++ b/tests/src/io_operators.cpp
@@ -0,0 +1,110 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iosfwd>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+inline namespace traits_extensions
+{
+
+ template<typename T, typename = void>
+ struct has_stream_input : std::false_type
+ {
+ };
+
+ template<typename T>
+ struct has_stream_input<T, std::void_t<decltype(std::declval<std::istream &>() >> std::declval<T &>())>> : std::true_type
+ {
+ };
+
+ template<typename T>
+ auto constexpr has_stream_input_v = has_stream_input<T>::value;
+
+ template<typename T, typename = void>
+ struct has_stream_output : std::false_type
+ {
+ };
+
+ template<typename T>
+ struct has_stream_output<T, std::void_t<decltype(std::declval<std::ostream &>() << std::declval<T &>())>> : std::true_type
+ {
+ };
+
+ template<typename T>
+ auto constexpr has_stream_output_v = has_stream_output<T>::value;
+
+} // namespace traits_extensions
+
+SCENARIO("Stream Input")
+{
+ GIVEN("A new_type over a stream-inputtable type deriving nt::Read")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Read)>;
+ static_assert(has_stream_input_v<type_alias::base_type>);
+
+ THEN("it has the stream input operator")
+ {
+ STATIC_REQUIRE(has_stream_input_v<type_alias>);
+ }
+
+ THEN("an instance of it can be read from an std::istream")
+ {
+ auto obj = type_alias{};
+ auto str = std::istringstream{"42"};
+
+ str >> obj;
+
+ REQUIRE(obj.decay() == 42);
+ }
+ }
+
+ GIVEN("A new_type over a non-stream-inputtable type deriving nt::Read")
+ {
+ using type_alias = nt::new_type<std::istream, struct tag, deriving(nt::Read)>;
+ static_assert(!has_stream_input_v<type_alias::base_type>);
+
+ THEN("it does not have the input operator")
+ {
+ STATIC_REQUIRE(!has_stream_input_v<type_alias>);
+ }
+ }
+}
+
+SCENARIO("Stream Output")
+{
+ GIVEN("A new_type over a stream-outputtable type deriving nt::Show")
+ {
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Show)>;
+ static_assert(has_stream_output_v<type_alias::base_type>);
+
+ THEN("it has the stream output operator")
+ {
+ STATIC_REQUIRE(has_stream_output_v<type_alias>);
+ }
+
+ THEN("an instance of it can be written to an std::ostream")
+ {
+ auto obj = type_alias{42};
+ auto str = std::ostringstream{};
+
+ str << obj;
+
+ REQUIRE(str.str() == "42");
+ }
+ }
+
+ GIVEN("A new_type over a non-stream-outputtable type deriving nt::Show")
+ {
+ using type_alias = nt::new_type<std::istream, struct tag, deriving(nt::Show)>;
+ static_assert(!has_stream_output_v<type_alias::base_type>);
+
+ THEN("it does not have the output operator")
+ {
+ STATIC_REQUIRE(!has_stream_output_v<type_alias>);
+ }
+ }
+}
diff --git a/tests/src/iterable.cpp b/tests/src/iterable.cpp
new file mode 100644
index 0000000..85b7edc
--- /dev/null
+++ b/tests/src/iterable.cpp
@@ -0,0 +1,1060 @@
+#include "newtype/newtype.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <numeric>
+
+namespace iterable_types
+{
+
+ struct with_member
+ {
+ using iterator = char *;
+ using const_iterator = char const *;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ auto constexpr begin() -> iterator;
+ auto constexpr begin() const -> const_iterator;
+ auto constexpr cbegin() const -> const_iterator;
+ auto constexpr rbegin() -> reverse_iterator;
+ auto constexpr rbegin() const -> const_reverse_iterator;
+ auto constexpr crbegin() const -> const_reverse_iterator;
+
+ auto constexpr end() -> iterator;
+ auto constexpr end() const -> const_iterator;
+ auto constexpr cend() const -> const_iterator;
+ auto constexpr rend() -> reverse_iterator;
+ auto constexpr rend() const -> const_reverse_iterator;
+ auto constexpr crend() const -> const_reverse_iterator;
+ };
+
+ struct with_free
+ {
+ using iterator = char *;
+ using const_iterator = char const *;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ };
+
+ auto constexpr begin(with_free &) -> with_free::iterator;
+ auto constexpr begin(with_free const &) -> with_free::const_iterator;
+ auto constexpr cbegin(with_free const &) -> with_free::const_iterator;
+ auto constexpr rbegin(with_free &) -> with_free::reverse_iterator;
+ auto constexpr rbegin(with_free const &) -> with_free::const_reverse_iterator;
+ auto constexpr crbegin(with_free const &) -> with_free::const_reverse_iterator;
+ auto constexpr end(with_free &) -> with_free::iterator;
+ auto constexpr end(with_free const &) -> with_free::const_iterator;
+ auto constexpr cend(with_free const &) -> with_free::const_iterator;
+ auto constexpr rend(with_free &) -> with_free::reverse_iterator;
+ auto constexpr rend(with_free const &) -> with_free::const_reverse_iterator;
+ auto constexpr crend(with_free const &) -> with_free::const_reverse_iterator;
+
+} // namespace iterable_types
+
+SCENARIO("Iterators", "[iterators]")
+{
+ GIVEN("A new_type over a non-iterable base type not deriving nt::Iterable")
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+ static_assert(!nt::concepts::beginnable<type_alias::base_type>);
+ static_assert(!nt::concepts::beginnable<type_alias::base_type const>);
+ static_assert(!nt::concepts::cbeginnable<type_alias::base_type>);
+ static_assert(!nt::concepts::rbeginnable<type_alias::base_type>);
+ static_assert(!nt::concepts::rbeginnable<type_alias::base_type const>);
+ static_assert(!nt::concepts::crbeginnable<type_alias::base_type>);
+ static_assert(!nt::concepts::endable<type_alias::base_type>);
+ static_assert(!nt::concepts::endable<type_alias::base_type const>);
+ static_assert(!nt::concepts::cendable<type_alias::base_type>);
+ static_assert(!nt::concepts::rendable<type_alias::base_type>);
+ static_assert(!nt::concepts::rendable<type_alias::base_type const>);
+ static_assert(!nt::concepts::crendable<type_alias::base_type>);
+
+ THEN("it has no begin")
+ {
+ STATIC_REQUIRE_FALSE(nt::concepts::beginnable<type_alias>);
+ }
+
+ TH