include/ylt/struct_pack/tuple.hpp (468 lines of code) (raw):

/* * Copyright (c) 2023, Alibaba Group Holding Limited; * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // https://github.com/codeinred/tuplet/blob/main/include/tuplet/tuple.hpp // commit id: ce4ab635c4f70b63ef9d19728bc8d76d71ae8685 // Use of this source code is governed by a Boost Software License that can be // found in the LICENSE file. #ifndef TUPLET_TUPLET_HPP_IMPLEMENTATION #define TUPLET_TUPLET_HPP_IMPLEMENTATION #include <compare> #include <cstddef> #include <string_view> #include <type_traits> #include <utility> #if (__has_cpp_attribute(no_unique_address)) #define TUPLET_NO_UNIQUE_ADDRESS [[no_unique_address]] #elif (__has_cpp_attribute(msvc::no_unique_address)) || \ ((defined _MSC_VER) && (!defined __clang__)) // Note __has_cpp_attribute(msvc::no_unique_address) itself doesn't work as // of 19.30.30709, even though the attribute itself is supported. See // https://github.com/llvm/llvm-project/issues/49358#issuecomment-981041089 #define TUPLET_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #else // no_unique_address is not available. #define TUPLET_NO_UNIQUE_ADDRESS #endif // tuplet concepts and traits namespace tuplet { template <class T> using identity_t = T; // Obtains T::type template <class T> using type_t = typename T::type; template <size_t I> using tag = std::integral_constant<size_t, I>; template <size_t I> constexpr tag<I> tag_v{}; template <size_t N> using tag_range = std::make_index_sequence<N>; template <class T, class U> concept same_as = std::is_same_v<T, U> && std::is_same_v<U, T>; template <class T, class U> concept other_than = !std::is_same_v<std::decay_t<T>, U>; template <class Tup> using base_list_t = typename std::decay_t<Tup>::base_list; template <class Tup> using element_list_t = typename std::decay_t<Tup>::element_list; template <class Tuple> concept base_list_tuple = requires() { typename std::decay_t<Tuple>::base_list; }; template <class T> concept stateless = std::is_empty_v<std::decay_t<T>>; template <class T> concept indexable = stateless<T> || requires(T t) { t[tag<0>()]; }; template <size_t I, indexable Tup> constexpr decltype(auto) get(Tup&& tup); template <class T, class U> concept other_than_tuple = !std::is_same_v<std::decay_t<T>, U> && (requires(U u) { get<U>(); }); template <class U, class T> concept assignable_to = requires(U u, T t) { t = u; }; template <class T> concept ordered = requires(T const& t) { {t <=> t}; }; template <class T> concept equality_comparable = requires(T const& t) { { t == t } -> same_as<bool>; }; } // namespace tuplet // tuplet::type_list implementation // tuplet::type_map implementation // tuplet::tuple_elem implementation // tuplet::deduce_elems namespace tuplet { template <class... T> struct tuple; template <class... T> struct type_list {}; template <class... Ls, class... Rs> constexpr auto operator+(type_list<Ls...>, type_list<Rs...>) { return type_list<Ls..., Rs...>{}; } template <class... Bases> struct type_map : Bases... { using base_list = type_list<Bases...>; using Bases::operator[]...; using Bases::decl_elem...; auto operator<=>(type_map const&) const = default; bool operator==(type_map const&) const = default; }; template <size_t I, class T> struct tuple_elem { // Like declval, but with the element static T decl_elem(tag<I>); using type = T; TUPLET_NO_UNIQUE_ADDRESS T value; constexpr decltype(auto) operator[](tag<I>) & { return (value); } constexpr decltype(auto) operator[](tag<I>) const& { return (value); } constexpr decltype(auto) operator[](tag<I>) && { return (std::move(*this).value); } auto operator<=>(tuple_elem const&) const = default; bool operator==(tuple_elem const&) const = default; // Implements comparison for tuples containing reference types constexpr auto operator<=>(tuple_elem const& other) const noexcept(noexcept( value <=> other.value)) requires(std::is_reference_v<T>&& ordered<T>) { return value <=> other.value; } constexpr bool operator==(tuple_elem const& other) const noexcept(noexcept(value == other.value)) requires( std::is_reference_v<T>&& equality_comparable<T>) { return value == other.value; } }; template <class T> using unwrap_ref_decay_t = typename std::unwrap_ref_decay<T>::type; } // namespace tuplet // tuplet::detail::get_tuple_base implementation // tuplet::detail::apply_impl // tuplet::detail::size_t_from_digits namespace tuplet::detail { template <class A, class... T> struct get_tuple_base; template <size_t... I, class... T> struct get_tuple_base<std::index_sequence<I...>, T...> { using type = type_map<tuple_elem<I, T>...>; }; template <class F, class T, class... Bases> constexpr decltype(auto) apply_impl(F&& f, T&& t, type_list<Bases...>) { return static_cast<F&&>(f)(static_cast<T&&>(t).identity_t<Bases>::value...); } template <char... D> constexpr size_t size_t_from_digits() { static_assert((('0' <= D && D <= '9') && ...), "Must be integral literal"); size_t num = 0; return ((num = num * 10 + (D - '0')), ..., num); } template <class First, class> using first_t = First; template <class T, class... Q> constexpr auto repeat_type(type_list<Q...>) { return type_list<first_t<T, Q>...>{}; } template <class... Outer> constexpr auto get_outer_bases(type_list<Outer...>) { return (repeat_type<Outer>(base_list_t<type_t<Outer>>{}) + ...); } template <class... Outer> constexpr auto get_inner_bases(type_list<Outer...>) { return (base_list_t<type_t<Outer>>{} + ...); } // This takes a forwarding tuple as a parameter. The forwarding tuple only // contains references, so it should just be taken by value. template <class T, class... Outer, class... Inner> constexpr auto cat_impl(T tup, type_list<Outer...>, type_list<Inner...>) -> tuple<type_t<Inner>...> { return {static_cast<type_t<Outer>&&>(tup.identity_t<Outer>::value) .identity_t<Inner>::value...}; } } // namespace tuplet::detail // tuplet::tuple implementation namespace tuplet { template <class... T> using tuple_base_t = typename detail::get_tuple_base<tag_range<sizeof...(T)>, T...>::type; template <class... T> struct tuple : tuple_base_t<T...> { constexpr static size_t N = sizeof...(T); using super = tuple_base_t<T...>; using super::operator[]; using base_list = typename super::base_list; using element_list = type_list<T...>; using super::decl_elem; template <other_than_tuple<tuple> U> // Preserves default assignments constexpr auto& operator=(U&& tup) { using tuple2 = std::decay_t<U>; if constexpr (base_list_tuple<tuple2>) { eq_impl(static_cast<U&&>(tup), base_list(), typename tuple2::base_list()); } else { eq_impl(static_cast<U&&>(tup), tag_range<N>()); } return *this; } template <assignable_to<T>... U> constexpr auto& assign(U&&... values) { assign_impl(base_list(), static_cast<U&&>(values)...); return *this; } auto operator<=>(tuple const&) const = default; bool operator==(tuple const&) const = default; // Applies a function to every element of the tuple. The order is the // declaration order, so first the function will be applied to element 0, // then element 1, then element 2, and so on, where element N is identified // by get<N> template <class F> constexpr void for_each(F&& func) & { for_each_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr void for_each(F&& func) const& { for_each_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr void for_each(F&& func) && { static_cast<tuple&&>(*this).for_each_impl(base_list(), static_cast<F&&>(func)); } // Applies a function to each element successively, until one returns a // truthy value. Returns true if any application returned a truthy value, // and false otherwise template <class F> constexpr bool any(F&& func) & { return any_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr bool any(F&& func) const& { return any_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr bool any(F&& func) && { return static_cast<tuple&&>(*this).any_impl(base_list(), static_cast<F&&>(func)); } // Applies a function to each element successively, until one returns a // falsy value. Returns true if every application returned a truthy value, // and false otherwise template <class F> constexpr bool all(F&& func) & { return all_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr bool all(F&& func) const& { return all_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr bool all(F&& func) && { return static_cast<tuple&&>(*this).all_impl(base_list(), static_cast<F&&>(func)); } // Map a function over every element in the tuple, using the values to // construct a new tuple template <class F> constexpr auto map(F&& func) & { return map_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr auto map(F&& func) const& { return map_impl(base_list(), static_cast<F&&>(func)); } template <class F> constexpr auto map(F&& func) && { return static_cast<tuple&&>(*this).map_impl(base_list(), static_cast<F&&>(func)); } private: template <class U, class... B1, class... B2> constexpr void eq_impl(U&& u, type_list<B1...>, type_list<B2...>) { // See: // https://developercommunity.visualstudio.com/t/fold-expressions-unreliable-in-171-with-c20/1676476 (void(B1::value = static_cast<U&&>(u).B2::value), ...); } template <class U, size_t... I> constexpr void eq_impl(U&& u, std::index_sequence<I...>) { (void(tuple_elem<I, T>::value = get<I>(static_cast<U&&>(u))), ...); } template <class... U, class... B> constexpr void assign_impl(type_list<B...>, U&&... u) { (void(B::value = static_cast<U&&>(u)), ...); } template <class F, class... B> constexpr void for_each_impl(type_list<B...>, F&& func) & { (void(func(B::value)), ...); } template <class F, class... B> constexpr void for_each_impl(type_list<B...>, F&& func) const& { (void(func(B::value)), ...); } template <class F, class... B> constexpr void for_each_impl(type_list<B...>, F&& func) && { (void(func(static_cast<T&&>(B::value))), ...); } template <class F, class... B> constexpr bool any_impl(type_list<B...>, F&& func) & { return (bool(func(B::value)) || ...); } template <class F, class... B> constexpr bool any_impl(type_list<B...>, F&& func) const& { return (bool(func(B::value)) || ...); } template <class F, class... B> constexpr bool any_impl(type_list<B...>, F&& func) && { return (bool(func(static_cast<T&&>(B::value))) || ...); } template <class F, class... B> constexpr bool all_impl(type_list<B...>, F&& func) & { return (bool(func(B::value)) && ...); } template <class F, class... B> constexpr bool all_impl(type_list<B...>, F&& func) const& { return (bool(func(B::value)) && ...); } template <class F, class... B> constexpr bool all_impl(type_list<B...>, F&& func) && { return (bool(func(static_cast<T&&>(B::value))) && ...); } template <class F, class... B> constexpr auto map_impl( type_list<B...>, F&& func) & -> tuple<unwrap_ref_decay_t<decltype(func(B::value))>...> { return {func(B::value)...}; } template <class F, class... B> constexpr auto map_impl(type_list<B...>, F&& func) const& -> tuple<unwrap_ref_decay_t<decltype(func(B::value))>...> { return {func(B::value)...}; } template <class F, class... B> constexpr auto map_impl(type_list<B...>, F&& func) && -> tuple< unwrap_ref_decay_t<decltype(func(static_cast<T&&>(B::value)))>...> { return {func(static_cast<T&&>(B::value))...}; } }; template <> struct tuple<> : tuple_base_t<> { constexpr static size_t N = 0; using super = tuple_base_t<>; using base_list = type_list<>; using element_list = type_list<>; template <other_than<tuple> U> // Preserves default assignments requires stateless<U> // Check that U is similarly stateless constexpr auto& operator=(U&&) noexcept { return *this; } constexpr auto& assign() noexcept { return *this; } auto operator<=>(tuple const&) const = default; bool operator==(tuple const&) const = default; // Applies a function to every element of the tuple. The order is the // declaration order, so first the function will be applied to element 0, // then element 1, then element 2, and so on, where element N is identified // by get<N> // // (Does nothing when invoked on empty tuple) template <class F> constexpr void for_each(F&&) const noexcept {} // Applies a function to each element successively, until one returns a // truthy value. Returns true if any application returned a truthy value, // and false otherwise // // (Returns false for empty tuple) template <class F> constexpr bool any(F&&) const noexcept { return false; } // Applies a function to each element successively, until one returns a // falsy value. Returns true if every application returned a truthy value, // and false otherwise // // (Returns true for empty tuple) template <class F> constexpr bool all(F&&) const noexcept { return true; } // Map a function over every element in the tuple, using the values to // construct a new tuple // // (Returns empty tuple when invoked on empty tuple) template <class F> constexpr auto map(F&&) const noexcept { return tuple{}; } }; template <class... Ts> tuple(Ts...) -> tuple<unwrap_ref_decay_t<Ts>...>; } // namespace tuplet // tuplet::pair implementation namespace tuplet { template <class First, class Second> struct pair { constexpr static size_t N = 2; TUPLET_NO_UNIQUE_ADDRESS First first; TUPLET_NO_UNIQUE_ADDRESS Second second; constexpr decltype(auto) operator[](tag<0>) & { return (first); } constexpr decltype(auto) operator[](tag<0>) const& { return (first); } constexpr decltype(auto) operator[](tag<0>) && { return (std::move(*this).first); } constexpr decltype(auto) operator[](tag<1>) & { return (second); } constexpr decltype(auto) operator[](tag<1>) const& { return (second); } constexpr decltype(auto) operator[](tag<1>) && { return (std::move(*this).second); } template <other_than<pair> Type> // Preserves default assignments constexpr auto& operator=(Type&& tup) { auto&& [a, b] = static_cast<Type&&>(tup); first = static_cast<decltype(a)&&>(a); second = static_cast<decltype(b)&&>(b); return *this; } template <assignable_to<First> F2, assignable_to<Second> S2> constexpr auto& assign(F2&& f, S2&& s) { first = static_cast<F2&&>(f); second = static_cast<S2&&>(s); return *this; } auto operator<=>(pair const&) const = default; bool operator==(pair const&) const = default; }; template <class A, class B> pair(A, B) -> pair<unwrap_ref_decay_t<A>, unwrap_ref_decay_t<B>>; } // namespace tuplet // tuplet::convert implementation namespace tuplet { // Converts from one tuple type to any other tuple or U template <class Tuple> struct convert { using base_list = typename std::decay_t<Tuple>::base_list; Tuple tuple; template <class U> constexpr operator U() && { return convert_impl<U>(base_list{}); } private: template <class U, class... Bases> constexpr U convert_impl(type_list<Bases...>) { return U{static_cast<Tuple&&>(tuple).identity_t<Bases>::value...}; } }; template <class Tuple> convert(Tuple&) -> convert<Tuple&>; template <class Tuple> convert(Tuple const&) -> convert<Tuple const&>; template <class Tuple> convert(Tuple&&) -> convert<Tuple>; } // namespace tuplet // tuplet::get implementation // tuplet::tie implementation // tuplet::apply implementation namespace tuplet { template <size_t I, indexable Tup> constexpr decltype(auto) get(Tup&& tup) { return static_cast<Tup&&>(tup)[tag<I>()]; } template <class... T> constexpr tuple<T&...> tie(T&... t) { return {t...}; } template <class F, base_list_tuple Tup> constexpr decltype(auto) apply(F&& func, Tup&& tup) { return detail::apply_impl(static_cast<F&&>(func), static_cast<Tup&&>(tup), typename std::decay_t<Tup>::base_list()); } template <class F, class A, class B> constexpr decltype(auto) apply(F&& func, tuplet::pair<A, B>& pair) { return static_cast<F&&>(func)(pair.first, pair.second); } template <class F, class A, class B> constexpr decltype(auto) apply(F&& func, tuplet::pair<A, B> const& pair) { return static_cast<F&&>(func)(pair.first, pair.second); } template <class F, class A, class B> constexpr decltype(auto) apply(F&& func, tuplet::pair<A, B>&& pair) { return static_cast<F&&>(func)(std::move(pair).first, std::move(pair).second); } } // namespace tuplet // tuplet::tuple_cat implementation // tuplet::make_tuple implementation // tuplet::forward_as_tuple implementation namespace tuplet { template <base_list_tuple... T> constexpr auto tuple_cat(T&&... ts) { if constexpr (sizeof...(T) == 0) { return tuple<>(); } else { /** * It appears that Clang produces better assembly when * TUPLET_CAT_BY_FORWARDING_TUPLE == 0, while GCC produces better assembly when * TUPLET_CAT_BY_FORWARDING_TUPLE == 1. MSVC always produces terrible assembly * in either case. This will set TUPLET_CAT_BY_FORWARDING_TUPLE to the correct * value (0 for clang, 1 for everyone else) * * See: https://github.com/codeinred/tuplet/discussions/14 */ #if !defined(TUPLET_CAT_BY_FORWARDING_TUPLE) #if defined(__clang__) #define TUPLET_CAT_BY_FORWARDING_TUPLE 0 #else #define TUPLET_CAT_BY_FORWARDING_TUPLE 1 #endif #endif #if TUPLET_CAT_BY_FORWARDING_TUPLE using big_tuple = tuple<T&&...>; #else using big_tuple = tuple<std::decay_t<T>...>; #endif using outer_bases = base_list_t<big_tuple>; constexpr auto outer = detail::get_outer_bases(outer_bases{}); constexpr auto inner = detail::get_inner_bases(outer_bases{}); return detail::cat_impl(big_tuple{static_cast<T&&>(ts)...}, outer, inner); } } template <typename... Ts> constexpr auto make_tuple(Ts&&... args) { return tuple<unwrap_ref_decay_t<Ts>...>{static_cast<Ts&&>(args)...}; } template <typename... T> constexpr auto forward_as_tuple(T&&... a) noexcept { return tuple<T&&...>{static_cast<T&&>(a)...}; } } // namespace tuplet // tuplet literals namespace tuplet::literals { template <char... D> constexpr auto operator""_tag() noexcept -> tag<detail::size_t_from_digits<D...>()> { return {}; } } // namespace tuplet::literals // std::tuple_size specialization // std::tuple_element specialization namespace std { template <class... T> struct tuple_size<tuplet::tuple<T...>> : std::integral_constant<size_t, sizeof...(T)> {}; template <size_t I, class... T> struct tuple_element<I, tuplet::tuple<T...>> { using type = decltype(tuplet::tuple<T...>::decl_elem(tuplet::tag<I>())); }; template <class A, class B> struct tuple_size<tuplet::pair<A, B>> : std::integral_constant<size_t, 2> {}; template <size_t I, class A, class B> struct tuple_element<I, tuplet::pair<A, B>> { static_assert(I < 2, "tuplet::pair only has 2 elements"); using type = std::conditional_t<I == 0, A, B>; }; } // namespace std namespace tuplet { template <class T> constexpr size_t tuple_size_v = std::tuple_size<T>::value; template <size_t I, class T> using tuple_element_t = typename std::tuple_element<I, T>::type; } // namespace tuplet #endif