AK: Add a Tuple implementation

Please don't use this outside of metaprogramming needs, *please*.
This commit is contained in:
Ali Mohammad Pur 2021-05-09 08:24:22 +04:30 committed by Linus Groh
parent 4fdbac236d
commit 02de813950
3 changed files with 328 additions and 0 deletions

218
AK/Tuple.h Normal file
View file

@ -0,0 +1,218 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/StdLibExtras.h>
#include <AK/TypeList.h>
namespace AK::Detail {
template<typename... Ts>
struct Tuple {
};
template<typename T>
struct Tuple<T> {
Tuple(T&& value) requires(!IsSame<T&&, const T&>)
: value(forward<T>(value))
{
}
Tuple(const T& value)
: value(value)
{
}
template<typename U>
U& get()
{
static_assert(IsSame<T, U>, "Invalid tuple access");
return value;
}
template<typename U>
const U& get() const
{
return const_cast<Tuple<T>&>(*this).get<U>();
}
template<typename U, unsigned index>
U& get_with_index()
{
static_assert(IsSame<T, U> && index == 0, "Invalid tuple access");
return value;
}
template<typename U, unsigned index>
const U& get_with_index() const
{
return const_cast<Tuple<T>&>(*this).get_with_index<U, index>();
}
private:
T value;
};
template<typename T, typename... TRest>
struct Tuple<T, TRest...> : Tuple<TRest...> {
Tuple(T&& first, TRest&&... rest)
: Tuple<TRest...>(forward<TRest>(rest)...)
, value(forward<T>(first))
{
}
Tuple(const T& first, const TRest&... rest)
: Tuple<TRest...>(rest...)
, value(first)
{
}
template<typename U>
U& get()
{
if constexpr (IsSame<T, U>)
return value;
else
return Tuple<TRest...>::template get<U>();
}
template<typename U>
const U& get() const
{
return const_cast<Tuple<T, TRest...>&>(*this).get<U>();
}
template<typename U, unsigned index>
U& get_with_index()
{
if constexpr (IsSame<T, U> && index == 0)
return value;
else
return Tuple<TRest...>::template get_with_index<U, index - 1>();
}
template<typename U, unsigned index>
const U& get_with_index() const
{
return const_cast<Tuple<T, TRest...>&>(*this).get_with_index<U, index>();
}
private:
T value;
};
}
namespace AK {
template<typename... Ts>
struct Tuple : Detail::Tuple<Ts...> {
using Types = TypeList<Ts...>;
using Detail::Tuple<Ts...>::Tuple;
using Indices = MakeIndexSequence<sizeof...(Ts)>;
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<typename T>
auto& get()
{
return Detail::Tuple<Ts...>::template get<T>();
}
template<unsigned index>
auto& get()
{
return Detail::Tuple<Ts...>::template get_with_index<typename Types::template Type<index>, index>();
}
template<typename T>
auto& get() const
{
return Detail::Tuple<Ts...>::template get<T>();
}
template<unsigned index>
auto& get() const
{
return Detail::Tuple<Ts...>::template get_with_index<typename Types::template Type<index>, index>();
}
template<typename F>
auto apply_as_args(F&& f)
{
return apply_as_args(forward<F>(f), Indices());
}
template<typename F>
auto apply_as_args(F&& f) const
{
return apply_as_args(forward<F>(f), Indices());
}
static constexpr auto size() { return sizeof...(Ts); }
private:
template<unsigned... Is>
Tuple(Tuple&& other, IndexSequence<Is...>)
: Detail::Tuple<Ts...>(move(other.get<Is>())...)
{
}
template<unsigned... Is>
Tuple(const Tuple& other, IndexSequence<Is...>)
: Detail::Tuple<Ts...>(other.get<Is>()...)
{
}
template<unsigned... Is>
void set(Tuple&& other, IndexSequence<Is...>)
{
((get<Is>() = move(other.get<Is>())), ...);
}
template<unsigned... Is>
void set(const Tuple& other, IndexSequence<Is...>)
{
((get<Is>() = other.get<Is>()), ...);
}
template<typename F, unsigned... Is>
auto apply_as_args(F&& f, IndexSequence<Is...>)
{
return forward<F>(f)(get<Is>()...);
}
template<typename F, unsigned... Is>
auto apply_as_args(F&& f, IndexSequence<Is...>) const
{
return forward<F>(f)(get<Is>()...);
}
};
}
using AK::Tuple;

View file

@ -52,6 +52,7 @@ set(AK_TEST_SOURCES
TestStringView.cpp
TestTime.cpp
TestTrie.cpp
TestTuple.cpp
TestTypeTraits.cpp
TestTypedTransfer.cpp
TestURL.cpp

109
Tests/AK/TestTuple.cpp Normal file
View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenity.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestSuite.h>
#include <AK/Tuple.h>
TEST_CASE(basic)
{
Tuple<int, String> value { 1, "foo" };
EXPECT_EQ(value.get<int>(), 1);
EXPECT_EQ(value.get<String>(), "foo");
EXPECT_EQ(value.get<0>(), 1);
EXPECT_EQ(value.get<1>(), "foo");
// Move assignment
value = { 2, "bar" };
EXPECT_EQ(value.get<int>(), 2);
EXPECT_EQ(value.get<String>(), "bar");
EXPECT_EQ(value.get<0>(), 2);
EXPECT_EQ(value.get<1>(), "bar");
// Copy ctor
auto other_value { value };
EXPECT_EQ(other_value.get<int>(), 2);
EXPECT_EQ(other_value.get<String>(), "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<int>(), 2);
EXPECT_EQ(moved_to_value.get<String>(), "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<int>(), 2);
EXPECT_EQ(moved_to_value.get<String>(), "bar");
EXPECT_EQ(moved_to_value.get<0>(), 2);
EXPECT_EQ(moved_to_value.get<1>(), "bar");
EXPECT_EQ(value.get<int>(), 2);
EXPECT_EQ(value.get<String>(), "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<NoCopy, int, int> value { {}, 1, 2 };
auto foo = move(value);
EXPECT_EQ(foo.get<1>(), 1);
EXPECT_EQ(foo.get<2>(), 2);
}
TEST_CASE(apply)
{
Tuple<int, int, String> 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);
}
}