diff --git a/AK/Tuple.h b/AK/Tuple.h new file mode 100644 index 0000000000..99fdd916da --- /dev/null +++ b/AK/Tuple.h @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace AK::Detail { + +template +struct Tuple { +}; + +template +struct Tuple { + Tuple(T&& value) requires(!IsSame) + : value(forward(value)) + { + } + + Tuple(const T& value) + : value(value) + { + } + + template + U& get() + { + static_assert(IsSame, "Invalid tuple access"); + return value; + } + + template + const U& get() const + { + return const_cast&>(*this).get(); + } + + template + U& get_with_index() + { + static_assert(IsSame && index == 0, "Invalid tuple access"); + return value; + } + + template + const U& get_with_index() const + { + return const_cast&>(*this).get_with_index(); + } + +private: + T value; +}; + +template +struct Tuple : Tuple { + Tuple(T&& first, TRest&&... rest) + : Tuple(forward(rest)...) + , value(forward(first)) + { + } + + Tuple(const T& first, const TRest&... rest) + : Tuple(rest...) + , value(first) + { + } + + template + U& get() + { + if constexpr (IsSame) + return value; + else + return Tuple::template get(); + } + + template + const U& get() const + { + return const_cast&>(*this).get(); + } + + template + U& get_with_index() + { + if constexpr (IsSame && index == 0) + return value; + else + return Tuple::template get_with_index(); + } + + template + const U& get_with_index() const + { + return const_cast&>(*this).get_with_index(); + } + +private: + T value; +}; + +} + +namespace AK { + +template +struct Tuple : Detail::Tuple { + using Types = TypeList; + using Detail::Tuple::Tuple; + using Indices = MakeIndexSequence; + + Tuple(Tuple&& other) + : Tuple(move(other), Indices()) + { + } + + Tuple(const Tuple& other) + : Tuple(other, Indices()) + { + } + + Tuple& operator=(Tuple&& other) + { + set(move(other), Indices()); + return *this; + } + + Tuple& operator=(const Tuple& other) + { + set(other, Indices()); + return *this; + } + + template + auto& get() + { + return Detail::Tuple::template get(); + } + + template + auto& get() + { + return Detail::Tuple::template get_with_index, index>(); + } + + template + auto& get() const + { + return Detail::Tuple::template get(); + } + + template + auto& get() const + { + return Detail::Tuple::template get_with_index, index>(); + } + + template + auto apply_as_args(F&& f) + { + return apply_as_args(forward(f), Indices()); + } + + template + auto apply_as_args(F&& f) const + { + return apply_as_args(forward(f), Indices()); + } + + static constexpr auto size() { return sizeof...(Ts); } + +private: + template + Tuple(Tuple&& other, IndexSequence) + : Detail::Tuple(move(other.get())...) + { + } + + template + Tuple(const Tuple& other, IndexSequence) + : Detail::Tuple(other.get()...) + { + } + + template + void set(Tuple&& other, IndexSequence) + { + ((get() = move(other.get())), ...); + } + + template + void set(const Tuple& other, IndexSequence) + { + ((get() = other.get()), ...); + } + + template + auto apply_as_args(F&& f, IndexSequence) + { + return forward(f)(get()...); + } + + template + auto apply_as_args(F&& f, IndexSequence) const + { + return forward(f)(get()...); + } +}; + +} + +using AK::Tuple; diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index ff0d6b0119..b74893f0f8 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -52,6 +52,7 @@ set(AK_TEST_SOURCES TestStringView.cpp TestTime.cpp TestTrie.cpp + TestTuple.cpp TestTypeTraits.cpp TestTypedTransfer.cpp TestURL.cpp diff --git a/Tests/AK/TestTuple.cpp b/Tests/AK/TestTuple.cpp new file mode 100644 index 0000000000..6084819ade --- /dev/null +++ b/Tests/AK/TestTuple.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include + +TEST_CASE(basic) +{ + Tuple value { 1, "foo" }; + EXPECT_EQ(value.get(), 1); + EXPECT_EQ(value.get(), "foo"); + EXPECT_EQ(value.get<0>(), 1); + EXPECT_EQ(value.get<1>(), "foo"); + + // Move assignment + value = { 2, "bar" }; + EXPECT_EQ(value.get(), 2); + EXPECT_EQ(value.get(), "bar"); + EXPECT_EQ(value.get<0>(), 2); + EXPECT_EQ(value.get<1>(), "bar"); + + // Copy ctor + auto other_value { value }; + EXPECT_EQ(other_value.get(), 2); + EXPECT_EQ(other_value.get(), "bar"); + EXPECT_EQ(other_value.get<0>(), 2); + EXPECT_EQ(other_value.get<1>(), "bar"); + + // Move ctor + auto moved_to_value { move(value) }; + EXPECT_EQ(moved_to_value.get(), 2); + EXPECT_EQ(moved_to_value.get(), "bar"); + EXPECT_EQ(moved_to_value.get<0>(), 2); + EXPECT_EQ(moved_to_value.get<1>(), "bar"); + + // Copy assignment + value = moved_to_value; + EXPECT_EQ(moved_to_value.get(), 2); + EXPECT_EQ(moved_to_value.get(), "bar"); + EXPECT_EQ(moved_to_value.get<0>(), 2); + EXPECT_EQ(moved_to_value.get<1>(), "bar"); + EXPECT_EQ(value.get(), 2); + EXPECT_EQ(value.get(), "bar"); + EXPECT_EQ(value.get<0>(), 2); + EXPECT_EQ(value.get<1>(), "bar"); +} + +TEST_CASE(no_copy) +{ + struct NoCopy { + AK_MAKE_NONCOPYABLE(NoCopy); + + public: + NoCopy(NoCopy&&) = default; + NoCopy() = default; + }; + + // Deleted copy ctor should not cause an issue so long as the value isn't copied. + Tuple value { {}, 1, 2 }; + auto foo = move(value); + EXPECT_EQ(foo.get<1>(), 1); + EXPECT_EQ(foo.get<2>(), 2); +} + +TEST_CASE(apply) +{ + Tuple args { 1, 2, "foo" }; + + // With copy + { + bool was_called = false; + args.apply_as_args([&](int a, int b, String c) { + was_called = true; + EXPECT_EQ(a, 1); + EXPECT_EQ(b, 2); + EXPECT_EQ(c, "foo"); + }); + EXPECT(was_called); + } + + // With reference + { + bool was_called = false; + args.apply_as_args([&](int& a, int& b, String& c) { + was_called = true; + EXPECT_EQ(a, 1); + EXPECT_EQ(b, 2); + EXPECT_EQ(c, "foo"); + }); + EXPECT(was_called); + } + + // With const reference, taken from a const tuple + { + bool was_called = false; + const auto& args_ref = args; + args_ref.apply_as_args([&](const int& a, const int& b, const String& c) { + was_called = true; + EXPECT_EQ(a, 1); + EXPECT_EQ(b, 2); + EXPECT_EQ(c, "foo"); + }); + EXPECT(was_called); + } +}