#include "detail/tuple_types.hpp" #include #include #include #include #include #include #include #include #include TEST_CASE("empty tuple", "[tuple]") { STATIC_CHECK(std::is_empty_v>); constexpr auto t = stdx::tuple{}; using T = std::remove_const_t; STATIC_CHECK(std::is_same_v>); STATIC_CHECK(stdx::tuple_size_v == 0); STATIC_CHECK(T::size() == 0); } TEST_CASE("single element tuple", "[tuple]") { constexpr auto t = stdx::tuple{1}; using T = std::remove_const_t; STATIC_CHECK(std::is_same_v>); STATIC_CHECK(stdx::tuple_size_v == 1); STATIC_CHECK(T::size() == 1); STATIC_CHECK(sizeof(T) == sizeof(int)); auto u = stdx::tuple{1}; using U = decltype(u); STATIC_CHECK(std::is_same_v>); STATIC_CHECK(stdx::tuple_size_v == 1); STATIC_CHECK(U::size() == 1); STATIC_CHECK(sizeof(U) == sizeof(int)); } TEST_CASE("multi element tuple", "[tuple]") { constexpr auto t = stdx::tuple{1, 2.0f}; using T = std::remove_const_t; STATIC_CHECK(std::is_same_v>); STATIC_CHECK(stdx::tuple_size_v == 2); STATIC_CHECK(T::size() == 2); } TEST_CASE("tuple is tuplelike", "[tuple]") { auto t = stdx::tuple{1, 2, 3}; STATIC_CHECK(stdx::tuplelike); STATIC_CHECK(stdx::tuplelike>); } TEST_CASE("tuple has tuple protocol", "[tuple]") { auto t = stdx::tuple{1, 2, 3}; STATIC_CHECK(stdx::has_tuple_protocol); STATIC_CHECK(stdx::has_tuple_protocol>); } TEST_CASE("std::array has tuple protocol", "[tuple_algorithms]") { auto t = std::array{1, 2, 3}; STATIC_CHECK(stdx::has_tuple_protocol); STATIC_CHECK(stdx::has_tuple_protocol>); } TEST_CASE("std::pair has tuple protocol", "[tuple_algorithms]") { auto t = std::pair{1, 2}; STATIC_CHECK(stdx::has_tuple_protocol); } namespace { template struct empty {}; } // namespace TEST_CASE("multi element tuple has minimal size", "[tuple]") { constexpr auto t = stdx::tuple{empty<0>{}, empty<1>{}}; STATIC_CHECK(sizeof(decltype(t)) == 1); constexpr auto u = stdx::tuple{42, empty<0>{}, empty<1>{}}; STATIC_CHECK(sizeof(decltype(u)) == sizeof(int)); } TEST_CASE("constexpr tuple of references", "[tuple]") { constexpr static int x = 1; constexpr auto t = stdx::tuple{x}; using T = std::remove_const_t; STATIC_CHECK(stdx::tuple_size_v == 1); STATIC_CHECK(T::size() == 1); } TEST_CASE("free get", "[tuple]") { constexpr auto t = stdx::tuple{5, true, 10l}; CHECK(stdx::get<0>(t) == 5); CHECK(stdx::get<1>(t)); CHECK(stdx::get<2>(t) == 10); STATIC_CHECK(stdx::get<0>(t) == 5); STATIC_CHECK(stdx::get<1>(t)); STATIC_CHECK(stdx::get<2>(t) == 10); CHECK(stdx::get(t) == 5); CHECK(stdx::get(t)); CHECK(stdx::get(t) == 10); STATIC_CHECK(stdx::get(t) == 5); STATIC_CHECK(stdx::get(t)); STATIC_CHECK(stdx::get(t) == 10); } TEST_CASE("free get (ADL)", "[tuple]") { constexpr auto t = stdx::tuple{5, true, 10l}; CHECK(get<0>(t) == 5); CHECK(get<1>(t)); CHECK(get<2>(t) == 10); STATIC_CHECK(get<0>(t) == 5); STATIC_CHECK(get<1>(t)); STATIC_CHECK(get<2>(t) == 10); CHECK(get(t) == 5); CHECK(get(t)); CHECK(get(t) == 10); STATIC_CHECK(get(t) == 5); STATIC_CHECK(get(t)); STATIC_CHECK(get(t) == 10); } TEST_CASE("free get value categories", "[tuple]") { { auto const t = stdx::tuple{42}; STATIC_CHECK(std::is_same_v(t)), int const &>); STATIC_CHECK(std::is_same_v(t)), int const &>); } { auto t = stdx::tuple{42}; STATIC_CHECK(std::is_same_v(t)), int &>); STATIC_CHECK(std::is_same_v(t)), int &>); STATIC_CHECK(std::is_same_v(std::move(t))), int &&>); STATIC_CHECK(std::is_same_v(std::move(t))), int &&>); } } TEST_CASE("indexing", "[tuple]") { using namespace stdx::literals; constexpr auto t = stdx::tuple{5, true, 10l}; STATIC_CHECK(t[0_idx] == 5); STATIC_CHECK(t[1_idx] == true); STATIC_CHECK(t[2_idx] == 10l); STATIC_CHECK(std::is_same_v); auto u = stdx::tuple{1}; STATIC_CHECK(std::is_same_v); STATIC_CHECK(std::is_same_v); } TEST_CASE("indexing with small literals", "[tuple]") { using namespace stdx::literals; constexpr auto t = stdx::tuple{5, true, 10l}; CHECK(get<"index"_0>(t) == 5); CHECK(get<"index"_1>(t)); CHECK(get<"index"_2>(t) == 10); STATIC_CHECK(t["index"_0] == 5); STATIC_CHECK(t["index"_1]); STATIC_CHECK(t["index"_2] == 10l); } TEST_CASE("tuple of lvalue references", "[tuple]") { auto x = 1; auto t = stdx::tuple{x}; CHECK(get<0>(t) == 1); get<0>(t) = 2; CHECK(get<0>(t) == 2); CHECK(x == 2); } TEST_CASE("tuple of lambdas", "[tuple]") { auto x = 1; auto t = stdx::tuple{[&] { x += 2; }, [&] { x += 3; }}; get<0>(t)(); CHECK(x == 3); } TEST_CASE("tuple size/elements", "[tuple]") { using T = stdx::tuple; STATIC_CHECK(stdx::tuple_size_v == 2); STATIC_CHECK(std::is_same_v, int>); STATIC_CHECK(std::is_same_v, bool>); using A = stdx::tuple; STATIC_CHECK(std::is_same_v, int &>); using B = stdx::tuple; STATIC_CHECK(std::is_same_v, int const &>); using C = stdx::tuple; STATIC_CHECK(std::is_same_v, int &&>); } TEST_CASE("destructuring", "[tuple]") { auto const t = stdx::tuple{1, 3.14f}; auto const [i, f] = t; CHECK(i == 1); CHECK(f == 3.14f); } namespace { struct A {}; struct B { B(int) {} }; } // namespace TEST_CASE("default constructability", "[tuple]") { STATIC_CHECK(std::is_default_constructible_v>); STATIC_CHECK(std::is_nothrow_default_constructible_v>); STATIC_CHECK(not std::is_default_constructible_v>); STATIC_CHECK(not std::is_nothrow_default_constructible_v>); } TEMPLATE_TEST_CASE("constructability", "[tuple]", (stdx::detail::element<0, int>), stdx::tuple<>, (stdx::tuple)) { STATIC_CHECK(std::is_default_constructible_v); STATIC_CHECK(std::is_nothrow_default_constructible_v); } TEMPLATE_TEST_CASE("copyability", "[tuple]", (stdx::detail::element<0, int>), stdx::tuple<>, (stdx::tuple)) { STATIC_CHECK(std::is_copy_constructible_v); STATIC_CHECK(std::is_copy_assignable_v); STATIC_CHECK(std::is_nothrow_copy_constructible_v); STATIC_CHECK(std::is_nothrow_copy_assignable_v); STATIC_CHECK(std::is_trivially_copy_constructible_v); STATIC_CHECK(std::is_trivially_copy_assignable_v); } TEMPLATE_TEST_CASE("moveability", "[tuple]", (stdx::detail::element<0, int>), stdx::tuple<>, (stdx::tuple)) { STATIC_CHECK(std::is_move_constructible_v); STATIC_CHECK(std::is_move_assignable_v); STATIC_CHECK(std::is_nothrow_move_constructible_v); STATIC_CHECK(std::is_nothrow_move_assignable_v); STATIC_CHECK(std::is_trivially_move_constructible_v); STATIC_CHECK(std::is_trivially_move_assignable_v); } TEMPLATE_TEST_CASE("destructability", "[tuple]", (stdx::detail::element<0, int>), stdx::tuple<>, (stdx::tuple)) { STATIC_CHECK(std::is_nothrow_destructible_v); STATIC_CHECK(std::is_trivially_destructible_v); } TEST_CASE("move-only types", "[tuple]") { STATIC_CHECK(std::is_default_constructible_v>); STATIC_CHECK( std::is_nothrow_default_constructible_v>); STATIC_CHECK(not std::is_copy_constructible_v>); STATIC_CHECK(not std::is_copy_assignable_v>); STATIC_CHECK(std::is_move_constructible_v>); STATIC_CHECK(std::is_move_assignable_v>); STATIC_CHECK(std::is_nothrow_move_constructible_v>); STATIC_CHECK(std::is_nothrow_move_assignable_v>); STATIC_CHECK( std::is_trivially_move_constructible_v>); STATIC_CHECK(std::is_trivially_move_assignable_v>); STATIC_CHECK(std::is_nothrow_destructible_v>); STATIC_CHECK(std::is_trivially_destructible_v>); } TEST_CASE("equality comparable", "[tuple]") { constexpr auto t = stdx::tuple{5, 10}; REQUIRE(t == t); REQUIRE(t != stdx::tuple{5, 11}); STATIC_CHECK(t == stdx::tuple{5, 10}); STATIC_CHECK(t != stdx::tuple{5, 11}); } TEST_CASE("equality comparable (tuple of references)", "[tuple]") { int x{5}; int y{5}; auto const t = stdx::tuple{x}; auto const u = stdx::tuple{y}; CHECK(t == t); CHECK(t == u); } TEST_CASE("equality comparable (references and non-references)", "[tuple]") { int x{5}; auto const t = stdx::tuple{x}; auto const u = stdx::tuple{5}; CHECK(t == u); CHECK(u == t); } TEST_CASE("equality comparable (conversions)", "[tuple]") { constexpr auto t = stdx::tuple{1}; STATIC_CHECK(t == stdx::tuple{1.0}); } namespace { struct eq { [[nodiscard]] friend constexpr auto operator==(eq lhs, eq rhs) -> bool { return lhs.x == rhs.x; } int x; }; struct eq_derived : eq {}; } // namespace TEST_CASE("equality comparable (user-defined)", "[tuple]") { constexpr auto t = stdx::tuple{eq{1}}; STATIC_CHECK(t == stdx::tuple{eq{1}}); STATIC_CHECK(t != stdx::tuple{eq{2}}); } TEST_CASE("equality comparable (conversions, user-defined)", "[tuple]") { constexpr auto t = stdx::tuple{eq{1}}; STATIC_CHECK(t == stdx::tuple{eq_derived{1}}); } TEST_CASE("order comparable", "[tuple]") { constexpr auto t = stdx::tuple{5, 10}; REQUIRE(t < stdx::tuple{6, 9}); REQUIRE(t < stdx::tuple{5, 11}); REQUIRE(not(t < t)); REQUIRE(not(t < stdx::tuple{4, 11})); STATIC_CHECK(t < stdx::tuple{6, 9}); STATIC_CHECK(t < stdx::tuple{5, 11}); STATIC_CHECK(not(t < t)); // NOLINT(misc-redundant-expression) STATIC_CHECK(not(t < stdx::tuple{4, 11})); REQUIRE(t <= t); REQUIRE(t <= stdx::tuple{6, 9}); REQUIRE(t <= stdx::tuple{5, 11}); REQUIRE(not(t <= stdx::tuple{5, 9})); REQUIRE(not(t <= stdx::tuple{4, 11})); STATIC_CHECK(t <= t); // NOLINT(misc-redundant-expression) STATIC_CHECK(t <= stdx::tuple{6, 9}); STATIC_CHECK(t <= stdx::tuple{5, 11}); STATIC_CHECK(not(t <= stdx::tuple{5, 9})); STATIC_CHECK(not(t <= stdx::tuple{4, 11})); REQUIRE(t > stdx::tuple{5, 9}); REQUIRE(t > stdx::tuple{4, 11}); REQUIRE(not(t > t)); REQUIRE(not(t > stdx::tuple{6, 9})); STATIC_CHECK(t > stdx::tuple{5, 9}); STATIC_CHECK(t > stdx::tuple{4, 11}); STATIC_CHECK(not(t > t)); // NOLINT(misc-redundant-expression) STATIC_CHECK(not(t > stdx::tuple{6, 9})); REQUIRE(t >= t); REQUIRE(t >= stdx::tuple{5, 9}); REQUIRE(t >= stdx::tuple{4, 11}); REQUIRE(not(t >= stdx::tuple{5, 11})); REQUIRE(not(t >= stdx::tuple{6, 9})); STATIC_CHECK(t >= t); // NOLINT(misc-redundant-expression) STATIC_CHECK(t >= stdx::tuple{5, 9}); STATIC_CHECK(t >= stdx::tuple{4, 11}); STATIC_CHECK(not(t >= stdx::tuple{5, 11})); STATIC_CHECK(not(t >= stdx::tuple{6, 9})); } TEST_CASE("order comparable (references and non-references)", "[tuple]") { int x{5}; int y{6}; auto const t = stdx::tuple{x, y}; auto const u = stdx::tuple{5, 7}; CHECK(t < u); CHECK(u > t); } TEST_CASE("spaceship comparable", "[tuple]") { constexpr auto t = stdx::tuple{5, 10}; REQUIRE(t <=> t == std::strong_ordering::equal); REQUIRE(t <=> stdx::tuple{6, 9} == std::strong_ordering::less); REQUIRE(t <=> stdx::tuple{6, 10} == std::strong_ordering::less); REQUIRE(t <=> stdx::tuple{5, 11} == std::strong_ordering::less); REQUIRE(t <=> stdx::tuple{5, 9} == std::strong_ordering::greater); REQUIRE(t <=> stdx::tuple{4, 10} == std::strong_ordering::greater); REQUIRE(t <=> stdx::tuple{4, 11} == std::strong_ordering::greater); STATIC_CHECK(t <=> t == std::strong_ordering::equal); STATIC_CHECK(t <=> stdx::tuple{6, 9} == std::strong_ordering::less); STATIC_CHECK(t <=> stdx::tuple{6, 10} == std::strong_ordering::less); STATIC_CHECK(t <=> stdx::tuple{5, 11} == std::strong_ordering::less); STATIC_CHECK(t <=> stdx::tuple{5, 9} == std::strong_ordering::greater); STATIC_CHECK(t <=> stdx::tuple{4, 10} == std::strong_ordering::greater); STATIC_CHECK(t <=> stdx::tuple{4, 11} == std::strong_ordering::greater); } TEST_CASE("spaceship comparable (references and non-references)", "[tuple]") { int x{5}; auto const t = stdx::tuple{x}; auto const u = stdx::tuple{5}; CHECK(t <=> u == std::strong_ordering::equal); CHECK(u <=> t == std::strong_ordering::equal); } TEST_CASE("free get is SFINAE-friendly", "[tuple]") { constexpr auto t = [](stdx::tuple const &tup) { return stdx::tuple{get(tup)...}; }(stdx::tuple{}); STATIC_CHECK(t == stdx::tuple{}); } TEST_CASE("copy/move behavior for tuple", "[tuple]") { auto t1 = stdx::tuple{counter{}}; counter::reset(); [[maybe_unused]] auto t2 = t1; CHECK(counter::moves == 0); CHECK(counter::copies == 1); counter::reset(); [[maybe_unused]] auto t3 = std::move(t1); CHECK(counter::moves == 1); CHECK(counter::copies == 0); } auto func_no_args() -> void; auto func_one_arg(int) -> void; TEST_CASE("make_tuple", "[tuple]") { STATIC_CHECK(stdx::make_tuple() == stdx::tuple{}); STATIC_CHECK(stdx::make_tuple(1, 2, 3) == stdx::tuple{1, 2, 3}); STATIC_CHECK( std::is_same_v>); constexpr auto t = stdx::make_tuple(stdx::tuple{}); using T = std::remove_const_t; STATIC_CHECK(std::is_same_v>>); STATIC_CHECK(stdx::tuple_size_v == 1); STATIC_CHECK(T::size() == 1); } namespace detail { template struct concat; template