summaryrefslogtreecommitdiff
path: root/asl/base
diff options
context:
space:
mode:
Diffstat (limited to 'asl/base')
-rw-r--r--asl/base/BUILD.bazel36
-rw-r--r--asl/base/annotations.hpp9
-rw-r--r--asl/base/assert.cpp12
-rw-r--r--asl/base/assert.hpp33
-rw-r--r--asl/base/config.hpp17
-rw-r--r--asl/base/float.hpp17
-rw-r--r--asl/base/float_tests.cpp23
-rw-r--r--asl/base/functional.hpp65
-rw-r--r--asl/base/functional_tests.cpp73
-rw-r--r--asl/base/integers.hpp40
-rw-r--r--asl/base/integers_tests.cpp15
-rw-r--r--asl/base/meta.hpp248
-rw-r--r--asl/base/meta_tests.cpp289
-rw-r--r--asl/base/utility.hpp95
-rw-r--r--asl/base/utility_tests.cpp1
15 files changed, 973 insertions, 0 deletions
diff --git a/asl/base/BUILD.bazel b/asl/base/BUILD.bazel
new file mode 100644
index 0000000..317c20b
--- /dev/null
+++ b/asl/base/BUILD.bazel
@@ -0,0 +1,36 @@
+cc_library(
+ name = "base",
+ hdrs = [
+ "annotations.hpp",
+ "assert.hpp",
+ "config.hpp",
+ "float.hpp",
+ "functional.hpp",
+ "integers.hpp",
+ "meta.hpp",
+ "utility.hpp",
+ ],
+ srcs = [
+ "assert.cpp",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+[cc_test(
+ name = "%s_tests" % name,
+ srcs = [
+ "%s_tests.cpp" % name,
+ ],
+ deps = [
+ ":base",
+ "//asl/tests:utils",
+ "//asl/testing",
+ "//asl/types:box",
+ ],
+) for name in [
+ "float",
+ "functional",
+ "integers",
+ "meta",
+ "utility",
+]]
diff --git a/asl/base/annotations.hpp b/asl/base/annotations.hpp
new file mode 100644
index 0000000..b87dbde
--- /dev/null
+++ b/asl/base/annotations.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "asl/base/config.hpp"
+
+#if ASL_COMPILER_CLANG_CL
+ #define ASL_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
+#elif ASL_COMPILER_CLANG
+ #define ASL_NO_UNIQUE_ADDRESS [[no_unique_address]]
+#endif
diff --git a/asl/base/assert.cpp b/asl/base/assert.cpp
new file mode 100644
index 0000000..2383e9e
--- /dev/null
+++ b/asl/base/assert.cpp
@@ -0,0 +1,12 @@
+#include "asl/base/assert.hpp"
+// #include "asl/io/print.hpp"
+
+
+void asl::report_assert_failure(const char* msg, const source_location& sl)
+{
+ // @Todo(org)
+ // eprint("------------------------------------------------------------\n");
+ // eprint("Assertion failure at {}, line {}:\n", sl.file, sl.line);
+ // eprint("{}\n", msg);
+ // eprint("------------------------------------------------------------\n");
+}
diff --git a/asl/base/assert.hpp b/asl/base/assert.hpp
new file mode 100644
index 0000000..42e8635
--- /dev/null
+++ b/asl/base/assert.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "asl/base/config.hpp"
+#include "asl/base/meta.hpp"
+
+namespace asl
+{
+
+void report_assert_failure(const char* msg, const source_location& sl = source_location{});
+
+} // namespace asl
+
+#if ASL_COMPILER_CLANG_CL
+ #define ASL_DEBUG_BREAK() __debugbreak()
+#elif ASL_COMPILER_CLANG
+ #define ASL_DEBUG_BREAK() __builtin_debugtrap()
+#endif
+
+#define ASL_ASSERT(...) \
+ if (__VA_ARGS__) {} \
+ else \
+ { \
+ ::asl::report_assert_failure(#__VA_ARGS__); \
+ ASL_DEBUG_BREAK(); \
+ }
+
+#define ASL_ASSERT_RELEASE(...) \
+ if (__VA_ARGS__) {} \
+ else \
+ { \
+ ::asl::report_assert_failure(#__VA_ARGS__); \
+ ASL_DEBUG_BREAK(); \
+ }
diff --git a/asl/base/config.hpp b/asl/base/config.hpp
new file mode 100644
index 0000000..e182569
--- /dev/null
+++ b/asl/base/config.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#if defined(_WIN32)
+ #define ASL_OS_WINDOWS 1
+#elif defined(__linux__)
+ #define ASL_OS_LINUX 1
+#else
+ #error Unknown OS
+#endif
+
+#if defined(__clang__) && defined(_MSC_VER)
+ #define ASL_COMPILER_CLANG_CL 1
+#elif defined(__clang__)
+ #define ASL_COMPILER_CLANG 1
+#else
+ #error Unknown compiler
+#endif
diff --git a/asl/base/float.hpp b/asl/base/float.hpp
new file mode 100644
index 0000000..2de2e0c
--- /dev/null
+++ b/asl/base/float.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "asl/base/meta.hpp"
+
+namespace asl
+{
+
+template<is_floating_point T> constexpr T infinity() { return __builtin_inf(); }
+
+template<is_floating_point T> constexpr T nan() { return static_cast<T>(__builtin_nanf("")); }
+
+template<is_floating_point T> constexpr bool is_infinity(T f) { return __builtin_isinf(f); }
+
+template<is_floating_point T> constexpr bool is_nan(T f) { return __builtin_isnan(f); }
+
+} // namespace asl
+
diff --git a/asl/base/float_tests.cpp b/asl/base/float_tests.cpp
new file mode 100644
index 0000000..0a5bebf
--- /dev/null
+++ b/asl/base/float_tests.cpp
@@ -0,0 +1,23 @@
+#include "asl/base/float.hpp"
+
+#include "asl/testing/testing.hpp"
+
+ASL_TEST(is_infinity)
+{
+ ASL_TEST_EXPECT(!asl::is_infinity(0.0F));
+ ASL_TEST_EXPECT(!asl::is_infinity(-25.0F));
+ ASL_TEST_EXPECT(asl::is_infinity(45.0F / 0.0F));
+ ASL_TEST_EXPECT(asl::is_infinity(-45.0F / 0.0F));
+ ASL_TEST_EXPECT(asl::is_infinity(asl::infinity<float>()));
+ ASL_TEST_EXPECT(asl::is_infinity(-asl::infinity<double>()));
+}
+
+ASL_TEST(is_nan)
+{
+ ASL_TEST_EXPECT(!asl::is_nan(0.0F));
+ ASL_TEST_EXPECT(!asl::is_nan(-25.0F));
+ ASL_TEST_EXPECT(!asl::is_nan(45.0F / 0.0F));
+ ASL_TEST_EXPECT(asl::is_nan(asl::nan<float>()));
+ ASL_TEST_EXPECT(asl::is_nan(asl::nan<double>()));
+}
+
diff --git a/asl/base/functional.hpp b/asl/base/functional.hpp
new file mode 100644
index 0000000..d820bce
--- /dev/null
+++ b/asl/base/functional.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "asl/base/meta.hpp"
+#include "asl/base/utility.hpp"
+
+namespace asl {
+
+template<typename... Args, typename C>
+constexpr auto invoke(is_func auto C::* f, auto&& self, Args&&... args)
+ requires requires {
+ (self.*f)(ASL_FWD(args)...);
+ }
+{
+ return (ASL_FWD(self).*f)(ASL_FWD(args)...);
+}
+
+template<typename... Args, typename C>
+constexpr auto invoke(is_func auto C::* f, auto* self, Args&&... args)
+ requires requires {
+ (self->*f)(ASL_FWD(args)...);
+ }
+{
+ return (self->*f)(ASL_FWD(args)...);
+}
+
+template<typename... Args, typename C>
+constexpr auto invoke(is_object auto C::* m, auto&& self, Args&&...)
+ requires (
+ sizeof...(Args) == 0 &&
+ requires { self.*m; }
+ )
+{
+ return ASL_FWD(self).*m;
+}
+
+template<typename... Args, typename C>
+constexpr auto invoke(is_object auto C::* m, auto* self, Args&&...)
+ requires (
+ sizeof...(Args) == 0 &&
+ requires { self->*m; }
+ )
+{
+ return self->*m;
+}
+
+template<typename... Args>
+constexpr auto invoke(auto&& f, Args&&... args)
+ requires requires {
+ f(ASL_FWD(args)...);
+ }
+{
+ return ASL_FWD(f)(ASL_FWD(args)...);
+}
+
+template<typename F> struct _result_of_helper;
+
+template<typename R, typename... Args>
+struct _result_of_helper<R(Args...)>
+{
+ using type = decltype(invoke(declval<R>(), declval<Args>()...));
+};
+
+template<typename F> using result_of_t = _result_of_helper<F>::type;
+
+} // namespace asl
diff --git a/asl/base/functional_tests.cpp b/asl/base/functional_tests.cpp
new file mode 100644
index 0000000..92c5c7b
--- /dev/null
+++ b/asl/base/functional_tests.cpp
@@ -0,0 +1,73 @@
+#include "asl/base/functional.hpp"
+#include "asl/testing/testing.hpp"
+
+struct HasFunction
+{
+ void do_something(int, float) {}
+};
+
+struct HasMember
+{
+ int member{};
+ int member_array[4]{};
+ void (*member_fn)(){};
+};
+
+struct Functor
+{
+ int64_t operator()() { return 35; }
+ int operator()(int x) { return x; }
+};
+
+static int some_func0() { return 1; }
+static int some_func1(int x) { return x + 1; }
+[[maybe_unused]] static float some_func1(float x) { return x + 1; }
+static int some_func2(int x, int b) { return x + b; }
+
+static_assert(asl::same_as<asl::result_of_t<Functor()>, int64_t>);
+static_assert(asl::same_as<asl::result_of_t<Functor(int)>, int>);
+static_assert(asl::same_as<asl::result_of_t<decltype(static_cast<float(*)(float)>(some_func1))(float)>, float>);
+static_assert(asl::same_as<asl::result_of_t<decltype(&HasFunction::do_something)(HasFunction, int, float)>, void>);
+static_assert(asl::same_as<asl::result_of_t<decltype(&HasMember::member)(HasMember)>, int>);
+
+ASL_TEST(invoke_member_function)
+{
+ HasFunction c;
+ asl::invoke(&HasFunction::do_something, c, 5, 5.0F);
+ asl::invoke(&HasFunction::do_something, &c, 5, 5.0F);
+}
+
+ASL_TEST(invoke_member_data)
+{
+ HasMember c;
+
+ asl::invoke(&HasMember::member, c);
+ asl::invoke(&HasMember::member_array, c);
+ asl::invoke(&HasMember::member_fn, c);
+ asl::invoke(&HasMember::member, &c);
+ asl::invoke(&HasMember::member_array, &c);
+ asl::invoke(&HasMember::member_fn, &c);
+}
+
+ASL_TEST(invoke_fn)
+{
+ ASL_TEST_EXPECT(asl::invoke(some_func0) == 1);
+ ASL_TEST_EXPECT(asl::invoke(static_cast<int(*)(int)>(some_func1), 8) == 9);
+ ASL_TEST_EXPECT(asl::invoke(some_func2, 4, 8) == 12);
+ ASL_TEST_EXPECT(asl::invoke(&some_func0) == 1);
+ ASL_TEST_EXPECT(asl::invoke(static_cast<int(*)(int)>(&some_func1), 8) == 9);
+ ASL_TEST_EXPECT(asl::invoke(&some_func2, 4, 8) == 12);
+}
+
+ASL_TEST(invoke_operator_call)
+{
+ Functor f;
+ ASL_TEST_EXPECT(asl::invoke(f) == 35);
+ ASL_TEST_EXPECT(asl::invoke(f, 8) == 8);
+}
+
+ASL_TEST(invoke_lambda)
+{
+ ASL_TEST_EXPECT(asl::invoke([](){ return 35; }) == 35);
+ ASL_TEST_EXPECT(asl::invoke([](int x){ return x + 2; }, 6) == 8);
+}
diff --git a/asl/base/integers.hpp b/asl/base/integers.hpp
new file mode 100644
index 0000000..c18c850
--- /dev/null
+++ b/asl/base/integers.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "asl/base/config.hpp"
+
+using int8_t = signed char;
+using int16_t = signed short;
+using int32_t = signed int;
+#if ASL_OS_WINDOWS
+ using int64_t = signed long long;
+#elif ASL_OS_LINUX
+ using int64_t = signed long;
+#endif
+
+using uint8_t = unsigned char;
+using uint16_t = unsigned short;
+using uint32_t = unsigned int;
+#if ASL_OS_WINDOWS
+ using uint64_t = unsigned long long;
+#elif ASL_OS_LINUX
+ using uint64_t = unsigned long;
+#endif
+
+struct uint128_t
+{
+ uint64_t high;
+ uint64_t low;
+};
+
+using size_t = uint64_t;
+using isize_t = int64_t;
+
+using uintptr_t = size_t;
+
+namespace asl
+{
+
+enum class byte : uint8_t {};
+
+} // namespace asl
+
diff --git a/asl/base/integers_tests.cpp b/asl/base/integers_tests.cpp
new file mode 100644
index 0000000..52feb85
--- /dev/null
+++ b/asl/base/integers_tests.cpp
@@ -0,0 +1,15 @@
+#include "asl/base/integers.hpp"
+
+static_assert(sizeof(int8_t) == 1);
+static_assert(sizeof(int16_t) == 2);
+static_assert(sizeof(int32_t) == 4);
+static_assert(sizeof(int64_t) == 8);
+
+static_assert(sizeof(uint8_t) == 1);
+static_assert(sizeof(uint16_t) == 2);
+static_assert(sizeof(uint32_t) == 4);
+static_assert(sizeof(uint64_t) == 8);
+
+static_assert(sizeof(asl::byte) == 1);
+
+static_assert(sizeof(uintptr_t) == sizeof(void*));
diff --git a/asl/base/meta.hpp b/asl/base/meta.hpp
new file mode 100644
index 0000000..ce17420
--- /dev/null
+++ b/asl/base/meta.hpp
@@ -0,0 +1,248 @@
+#pragma once
+
+#include "asl/base/integers.hpp"
+
+namespace asl {
+
+struct source_location
+{
+ const char* file;
+ int line;
+
+ explicit source_location(
+ const char* file_ = __builtin_FILE(),
+ int line_ = __builtin_LINE())
+ : file{file_}
+ , line{line_}
+ {}
+};
+
+struct empty {};
+
+template<typename T> struct id { using type = T; };
+
+template<typename... Args> static constexpr isize_t types_count = sizeof...(Args);
+
+template<typename T, T kValue> struct integral_constant { static constexpr T value = kValue; };
+template<bool B> using bool_constant = integral_constant<bool, B>;
+
+using true_type = bool_constant<true>;
+using false_type = bool_constant<false>;
+
+template<bool kSelect, typename U, typename V> struct _select_helper { using type = V; };
+template<typename U, typename V> struct _select_helper<true, U, V> { using type = U; };
+
+template<bool kSelect, typename U, typename V> using select_t = _select_helper<kSelect, U, V>::type;
+
+template<typename U, typename V> struct _same_as_helper : false_type {};
+template<typename T> struct _same_as_helper<T, T> : true_type {};
+
+template<typename U, typename V> concept same_as = _same_as_helper<U, V>::value && _same_as_helper<V, U>::value;
+
+template<typename T> auto _as_lref_helper(int) -> id<T&>;
+template<typename T> auto _as_lref_helper(...) -> id<T>;
+
+template<typename T> auto _as_rref_helper(int) -> id<T&&>;
+template<typename T> auto _as_rref_helper(...) -> id<T>;
+
+template<typename T> using as_lref_t = decltype(_as_lref_helper<T>(0))::type;
+template<typename T> using as_rref_t = decltype(_as_rref_helper<T>(0))::type;
+
+template<typename T> consteval as_rref_t<T> declval() {}
+
+template<typename T> struct _un_ref_t { using type = T; };
+template<typename T> struct _un_ref_t<T&> { using type = T; };
+template<typename T> struct _un_ref_t<T&&> { using type = T; };
+
+template<typename T> using un_ref_t = _un_ref_t<T>::type;
+
+template<typename T, typename... Args> concept constructible_from = __is_constructible(T, Args...);
+
+template<typename T> concept default_constructible = constructible_from<T>;
+template<typename T> concept copy_constructible = constructible_from<T, as_lref_t<const T>>;
+template<typename T> concept move_constructible = constructible_from<T, as_rref_t<T>>;
+
+template<typename T, typename... Args> concept trivially_constructible_from = __is_trivially_constructible(T, Args...);
+
+template<typename T> concept trivially_default_constructible = trivially_constructible_from<T>;
+template<typename T> concept trivially_copy_constructible = trivially_constructible_from<T, as_lref_t<const T>>;
+template<typename T> concept trivially_move_constructible = trivially_constructible_from<T, as_rref_t<T>>;
+
+template<typename T, typename... Args> concept assignable_from = __is_assignable(T, Args...);
+
+template<typename T> concept copy_assignable = assignable_from<as_lref_t<T>, as_lref_t<const T>>;
+template<typename T> concept move_assignable = assignable_from<as_lref_t<T>, as_rref_t<T>>;
+
+template<typename T, typename... Args> concept trivially_assignable_from = __is_trivially_assignable(T, Args...);
+
+template<typename T> concept trivially_copy_assignable = trivially_assignable_from<as_lref_t<T>, as_lref_t<const T>>;
+template<typename T> concept trivially_move_assignable = trivially_assignable_from<as_lref_t<T>, as_rref_t<T>>;
+
+template<typename T> concept trivially_destructible = __is_trivially_destructible(T);
+
+template<typename T> concept copyable = copy_constructible<T> && copy_assignable<T>;
+template<typename T> concept moveable = move_constructible<T> && move_assignable<T>;
+
+template<typename To, typename From>
+concept convertible_from = __is_convertible(From, To);
+
+template<typename Derived, class Base>
+concept derived_from = __is_class(Derived) && __is_class(Base) && convertible_from<const volatile Base*, const volatile Derived*>;
+
+using nullptr_t = decltype(nullptr);
+
+template<typename T> struct _un_const_helper { using type = T; };
+template<typename T> struct _un_const_helper<const T> { using type = T; };
+
+template<typename T> using un_const_t = _un_const_helper<T>::type;
+
+template<typename T> struct _is_const_helper : false_type {};
+template<typename T> struct _is_const_helper<const T> : true_type {};
+
+template<typename T> concept is_const = _is_const_helper<T>::value;
+
+template<typename T> struct _un_volatile_helper { using type = T; };
+template<typename T> struct _un_volatile_helper<volatile T> { using type = T; };
+
+template<typename T> using un_volatile_t = _un_volatile_helper<T>::type;
+
+template<typename T> using un_cv_t = un_volatile_t<un_const_t<T>>;
+
+template<typename T> using un_cvref_t = un_ref_t<un_cv_t<T>>;
+
+template<typename T> concept is_void = same_as<void, un_cv_t<T>>;
+
+template<typename T> struct _is_ref_helper { static constexpr bool l = false; static constexpr bool r = false; };
+template<typename T> struct _is_ref_helper<T&> { static constexpr bool l = true; static constexpr bool r = false; };
+template<typename T> struct _is_ref_helper<T&&> { static constexpr bool l = false; static constexpr bool r = true; };
+
+template<typename T> concept is_ref = _is_ref_helper<T>::l || _is_ref_helper<T>::r;
+template<typename T> concept is_rref = _is_ref_helper<T>::r;
+template<typename T> concept is_lref = _is_ref_helper<T>::l;
+
+template<typename T> struct _is_ptr_helper : false_type {};
+template<typename T> struct _is_ptr_helper<T*> : true_type {};
+
+template<typename T> concept is_ptr = _is_ptr_helper<un_cv_t<T>>::value;
+
+template<typename T> struct _tame_helper { using type = T; };
+
+#define TAME_HELPER_IMPL(TRAILING) \
+ template<typename R, typename... Args> \
+ struct _tame_helper<R(Args...) TRAILING> { using type = R(Args...); }
+
+TAME_HELPER_IMPL();
+TAME_HELPER_IMPL(&);
+TAME_HELPER_IMPL(&&);
+TAME_HELPER_IMPL(const);
+TAME_HELPER_IMPL(const &);
+TAME_HELPER_IMPL(const &&);
+TAME_HELPER_IMPL(volatile);
+TAME_HELPER_IMPL(volatile &);
+TAME_HELPER_IMPL(volatile &&);
+TAME_HELPER_IMPL(const volatile);
+TAME_HELPER_IMPL(const volatile &);
+TAME_HELPER_IMPL(const volatile &&);
+TAME_HELPER_IMPL(noexcept);
+TAME_HELPER_IMPL(& noexcept);
+TAME_HELPER_IMPL(&& noexcept);
+TAME_HELPER_IMPL(const noexcept);
+TAME_HELPER_IMPL(const & noexcept);
+TAME_HELPER_IMPL(const && noexcept);
+TAME_HELPER_IMPL(volatile noexcept);
+TAME_HELPER_IMPL(volatile & noexcept);
+TAME_HELPER_IMPL(volatile && noexcept);
+TAME_HELPER_IMPL(const volatile noexcept);
+TAME_HELPER_IMPL(const volatile & noexcept);
+TAME_HELPER_IMPL(const volatile && noexcept);
+
+#undef TAME_HELPER_IMPL
+
+template<typename T> using tame_t = _tame_helper<T>::type;
+
+template<typename T> struct _is_func_helper : false_type {};
+template<typename R, typename... Args> struct _is_func_helper<R(Args...)> : true_type {};
+
+template<typename T> concept is_func = _is_func_helper<tame_t<T>>::value;
+
+template<typename T> concept is_object = !is_void<T> && !is_ref<T> && !is_func<T>;
+
+template<typename T> struct _is_array_helper : false_type {};
+template<typename T> struct _is_array_helper<T[]> : true_type {};
+template<typename T, int N> struct _is_array_helper<T[N]> : true_type {};
+
+template<typename T> concept is_array = _is_array_helper<T>::value;
+
+template<typename T> struct _is_floating_point_helper : false_type {};
+template<> struct _is_floating_point_helper<float> : true_type {};
+template<> struct _is_floating_point_helper<double> : true_type {};
+
+template<typename T> concept is_floating_point = _is_floating_point_helper<un_cv_t<T>>::value;
+
+template<typename T> struct _is_integer_helper : false_type {};
+template<> struct _is_integer_helper<int8_t> : true_type {};
+template<> struct _is_integer_helper<int16_t> : true_type {};
+template<> struct _is_integer_helper<int32_t> : true_type {};
+template<> struct _is_integer_helper<int64_t> : true_type {};
+template<> struct _is_integer_helper<uint8_t> : true_type {};
+template<> struct _is_integer_helper<uint16_t> : true_type {};
+template<> struct _is_integer_helper<uint32_t> : true_type {};
+template<> struct _is_integer_helper<uint64_t> : true_type {};
+
+template<typename T> concept is_integer = _is_integer_helper<un_cv_t<T>>::value;
+
+template<typename T> concept is_enum = __is_enum(T);
+
+template<typename T> struct is_uniquely_represented : false_type {};
+template<is_integer T> struct is_uniquely_represented<T> : true_type {};
+template<is_enum T> struct is_uniquely_represented<T> : true_type {};
+template<> struct is_uniquely_represented<uint128_t> : true_type {};
+template<> struct is_uniquely_represented<byte> : true_type {};
+
+template<typename T> concept uniquely_represented = is_uniquely_represented<un_cv_t<T>>::value;
+
+template<typename T, typename U>
+concept equality_comparable_with = requires (const un_cvref_t<T>& a, const un_cvref_t<U>& b)
+{
+ { a == b } -> same_as<bool>;
+ { b == a } -> same_as<bool>;
+ { a != b } -> same_as<bool>;
+ { b != a } -> same_as<bool>;
+};
+
+template<typename T> concept equality_comparable = equality_comparable_with<T, T>;
+
+struct niche_t {};
+
+template<typename T>
+concept has_niche = constructible_from<T, niche_t> && equality_comparable_with<T, niche_t>;
+
+template<typename T>
+concept is_niche = same_as<un_cvref_t<T>, niche_t>;
+
+template<typename T, typename U>
+concept _derefs_with_indirection_as = requires(T& t)
+{
+ *t;
+ requires convertible_from<U&, decltype(*t)>;
+};
+
+template<typename T, typename U>
+concept _derefs_reference_as = is_ref<T> && convertible_from<U&, T>;
+
+template<typename T, typename U>
+concept _derefs_value_as = !is_ref<T> && convertible_from<U&, T&>;
+
+template<typename U, _derefs_with_indirection_as<U> T>
+constexpr U& deref(T&& t) { return static_cast<U&>(*t); }
+
+template<typename U, _derefs_reference_as<U> T>
+constexpr U& deref(T&& t) { return static_cast<U&>(t); }
+
+template<typename U, _derefs_value_as<U> T>
+constexpr U& deref(T&& t) { return static_cast<U&>(t); }
+
+template<typename T, typename U>
+concept derefs_as = _derefs_with_indirection_as<T, U> || _derefs_reference_as<T, U> || _derefs_value_as<T, U>;
+
+} // namespace asl
diff --git a/asl/base/meta_tests.cpp b/asl/base/meta_tests.cpp
new file mode 100644
index 0000000..7aa7145
--- /dev/null
+++ b/asl/base/meta_tests.cpp
@@ -0,0 +1,289 @@
+#include "asl/base/meta.hpp"
+#include "asl/tests/types.hpp"
+#include "asl/testing/testing.hpp"
+#include "asl/types/box.hpp"
+
+struct Struct {};
+union Union {};
+enum Enum : uint8_t { EnumVariant = 0, };
+enum class EnumClass : uint8_t { Variant = 0, };
+
+static_assert(!asl::same_as<long, short>);
+static_assert(asl::same_as<int, int>);
+
+static_assert(asl::same_as<asl::select_t<false, int, float>, float>);
+static_assert(asl::same_as<asl::select_t<true, int, float>, int>);
+
+static_assert(asl::default_constructible<int>);
+static_assert(asl::default_constructible<TrivialType>);
+static_assert(asl::default_constructible<TrivialTypeDefaultValue>);
+
+static_assert(asl::trivially_default_constructible<int>);
+static_assert(asl::trivially_default_constructible<TrivialType>);
+static_assert(!asl::trivially_default_constructible<TrivialTypeDefaultValue>);
+
+static_assert(asl::copy_constructible<int>);
+static_assert(asl::copy_constructible<TrivialType>);
+static_assert(asl::copy_constructible<Copyable>);
+static_assert(!asl::copy_constructible<MoveableOnly>);
+static_assert(!asl::copy_constructible<Pinned>);
+
+static_assert(asl::trivially_copy_constructible<int>);
+static_assert(asl::trivially_copy_constructible<TrivialType>);
+static_assert(asl::trivially_copy_constructible<TrivialTypeDefaultValue>);
+static_assert(!asl::trivially_copy_constructible<WithDestructor>);
+static_assert(!asl::trivially_copy_constructible<Copyable>);
+static_assert(!asl::trivially_copy_constructible<MoveableOnly>);
+static_assert(!asl::trivially_copy_constructible<Pinned>);
+
+static_assert(asl::move_constructible<int>);
+static_assert(asl::move_constructible<TrivialType>);
+static_assert(asl::move_constructible<Copyable>);
+static_assert(asl::move_constructible<MoveableOnly>);
+static_assert(!asl::move_constructible<Pinned>);
+
+static_assert(asl::trivially_move_constructible<int>);
+static_assert(asl::trivially_move_constructible<TrivialType>);
+static_assert(asl::trivially_move_constructible<TrivialTypeDefaultValue>);
+static_assert(!asl::trivially_move_constructible<WithDestructor>);
+static_assert(!asl::trivially_move_constructible<Copyable>);
+static_assert(!asl::trivially_move_constructible<MoveableOnly>);
+static_assert(!asl::trivially_move_constructible<Pinned>);
+
+static_assert(asl::copy_assignable<int>);
+static_assert(asl::copy_assignable<TrivialType>);
+static_assert(asl::copy_assignable<Copyable>);
+static_assert(!asl::copy_assignable<MoveableOnly>);
+static_assert(!asl::copy_assignable<Pinned>);
+
+static_assert(asl::trivially_copy_assignable<int>);
+static_assert(asl::trivially_copy_assignable<TrivialType>);
+static_assert(asl::trivially_copy_assignable<TrivialTypeDefaultValue>);
+static_assert(asl::trivially_copy_assignable<WithDestructor>);
+static_assert(!asl::trivially_copy_assignable<Copyable>);
+static_assert(!asl::trivially_copy_assignable<MoveableOnly>);
+static_assert(!asl::trivially_copy_assignable<Pinned>);
+
+static_assert(asl::copyable<int>);
+static_assert(asl::copyable<TrivialType>);
+static_assert(asl::copyable<Copyable>);
+static_assert(!asl::copyable<MoveableOnly>);
+static_assert(!asl::copyable<Pinned>);
+
+static_assert(asl::moveable<int>);
+static_assert(asl::moveable<TrivialType>);
+static_assert(asl::moveable<Copyable>);
+static_assert(asl::moveable<MoveableOnly>);
+static_assert(!asl::moveable<Pinned>);
+
+static_assert(asl::move_assignable<int>);
+static_assert(asl::move_assignable<TrivialType>);
+static_assert(asl::move_assignable<Copyable>);
+static_assert(asl::move_assignable<MoveableOnly>);
+static_assert(!asl::move_assignable<Pinned>);
+
+static_assert(asl::trivially_move_assignable<int>);
+static_assert(asl::trivially_move_assignable<TrivialType>);
+static_assert(asl::trivially_move_assignable<TrivialTypeDefaultValue>);
+static_assert(asl::trivially_move_assignable<WithDestructor>);
+static_assert(!asl::trivially_move_assignable<Copyable>);
+static_assert(!asl::trivially_move_assignable<MoveableOnly>);
+static_assert(!asl::trivially_move_assignable<Pinned>);
+
+static_assert(asl::trivially_destructible<int>);
+static_assert(asl::trivially_destructible<TrivialType>);
+static_assert(asl::trivially_destructible<TrivialTypeDefaultValue>);
+static_assert(!asl::trivially_destructible<WithDestructor>);
+static_assert(asl::trivially_destructible<Copyable>);
+static_assert(asl::trivially_destructible<MoveableOnly>);
+static_assert(asl::trivially_destructible<Pinned>);
+
+static_assert(asl::same_as<int, asl::un_const_t<int>>);
+static_assert(asl::same_as<int, asl::un_const_t<const int>>);
+static_assert(asl::same_as<const int&, asl::un_const_t<const int&>>);
+
+static_assert(asl::same_as<int, asl::un_volatile_t<int>>);
+static_assert(asl::same_as<int, asl::un_volatile_t<volatile int>>);
+static_assert(asl::same_as<volatile int&, asl::un_volatile_t<volatile int&>>);
+
+static_assert(asl::same_as<int, asl::un_cv_t<int>>);
+static_assert(asl::same_as<int, asl::un_cv_t<const int>>);
+static_assert(asl::same_as<int, asl::un_cv_t<const volatile int>>);
+static_assert(asl::same_as<int, asl::un_cv_t<volatile int>>);
+
+static_assert(asl::is_void<void>);
+static_assert(asl::is_void<const void>);
+static_assert(asl::is_void<const volatile void>);
+static_assert(asl::is_void<volatile void>);
+static_assert(!asl::is_void<int>);
+static_assert(!asl::is_void<Struct>);
+static_assert(!asl::is_void<int&>);
+static_assert(!asl::is_void<int&&>);
+static_assert(!asl::is_void<void()>);
+static_assert(!asl::is_void<void() const &&>);
+
+static_assert(asl::is_ref<int&>);
+static_assert(asl::is_ref<const int&>);
+static_assert(asl::is_ref<const volatile int&>);
+static_assert(asl::is_ref<int&&>);
+static_assert(!asl::is_ref<int>);
+static_assert(!asl::is_ref<void>);
+static_assert(!asl::is_ref<void()>);
+static_assert(!asl::is_ref<void() const &&>);
+
+static_assert(asl::is_ptr<int*>);
+static_assert(asl::is_ptr<const int* const>);
+static_assert(asl::is_ptr<const volatile int*>);
+static_assert(!asl::is_ptr<int>);
+static_assert(!asl::is_ptr<void>);
+static_assert(!asl::is_ptr<void()>);
+static_assert(!asl::is_ptr<void() const &&>);
+
+static_assert(asl::same_as<int, asl::tame_t<int>>);
+static_assert(asl::same_as<int(), asl::tame_t<int()>>);
+static_assert(asl::same_as<int(float), asl::tame_t<int(float)>>);
+static_assert(asl::same_as<int(float), asl::tame_t<int(float) &>>);
+static_assert(asl::same_as<int(float), asl::tame_t<int(float) const &&>>);
+static_assert(asl::same_as<int(float), asl::tame_t<int(float) volatile noexcept>>);
+static_assert(asl::same_as<int(float), asl::tame_t<int(float) && noexcept>>);
+static_assert(asl::same_as<int(float), asl::tame_t<int(float) const>>);
+
+static_assert(asl::is_func<void()>);
+static_assert(asl::is_func<void(int)>);
+static_assert(asl::is_func<void(int, float)>);
+static_assert(asl::is_func<void() &>);
+static_assert(asl::is_func<void() const &&>);
+static_assert(asl::is_func<void() volatile noexcept>);
+static_assert(!asl::is_func<void(*)()>);
+static_assert(!asl::is_func<int>);
+static_assert(!asl::is_func<int&>);
+static_assert(!asl::is_func<void>);
+
+static_assert(asl::is_object<Struct>);
+static_assert(asl::is_object<int>);
+static_assert(asl::is_object<int*>);
+static_assert(asl::is_object<int Struct::*>);
+static_assert(asl::is_object<int (Struct::*)(float)>);
+static_assert(asl::is_object<int[]>);
+static_assert(asl::is_object<int[45]>);
+static_assert(asl::is_object<Enum>);
+static_assert(!asl::is_object<int&>);
+static_assert(!asl::is_object<void>);
+static_assert(!asl::is_object<void(int)>);
+static_assert(!asl::is_object<int(float) const && noexcept>);
+
+static_assert(!asl::is_array<Struct>);
+static_assert(!asl::is_array<int>);
+static_assert(!asl::is_array<int*>);
+static_assert(!asl::is_array<int Struct::*>);
+static_assert(!asl::is_array<int (Struct::*)(float)>);
+static_assert(asl::is_array<int[]>);
+static_assert(asl::is_array<int[45]>);
+static_assert(!asl::is_array<Enum>);
+static_assert(!asl::is_array<int&>);
+static_assert(!asl::is_array<void>);
+static_assert(!asl::is_array<void(int)>);
+static_assert(!asl::is_array<int(float) const && noexcept>);
+
+static_assert(asl::same_as<int, asl::un_ref_t<int>>);
+static_assert(asl::same_as<int, asl::un_ref_t<int&>>);
+static_assert(asl::same_as<int, asl::un_ref_t<int&&>>);
+static_assert(asl::same_as<int() &, asl::un_ref_t<int() &>>);
+
+static_assert(asl::types_count<int, float> == 2);
+static_assert(asl::types_count<int, int> == 2);
+static_assert(asl::types_count<int> == 1);
+static_assert(asl::types_count<> == 0);
+
+class Base {};
+class Derived : public Base {};
+class C {};
+class D { public: operator C() { return c; } C c; }; // NOLINT
+class E { public: template<class T> E(T&&) {} }; // NOLINT
+
+static_assert(asl::convertible_from<Base*, Derived*>);
+static_assert(!asl::convertible_from<Derived*, Base*>);
+static_assert(asl::convertible_from<C, D>);
+static_assert(!asl::convertible_from<C*, Derived*>);
+static_assert(asl::convertible_from<E, Base>);
+
+static_assert(!asl::convertible_from<int16_t(&)[], int32_t(&)[]>);
+static_assert(asl::convertible_from<const int16_t(&)[], int16_t(&)[]>);
+static_assert(asl::convertible_from<const int16_t(&)[], const int16_t(&)[]>);
+static_assert(asl::convertible_from<int16_t(&)[], int16_t(&)[]>);
+static_assert(!asl::convertible_from<int32_t(&)[], int16_t(&)[]>);
+static_assert(!asl::