Last active
June 8, 2025 23:19
-
-
Save dk949/489f93c99e7308b7b3a97755b3125c70 to your computer and use it in GitHub Desktop.
std::pair alternative with extra features and no constructors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef UT_PAIR_HPP | |
| #define UT_PAIR_HPP | |
| #if __cplusplus < 202'002L | |
| # error this file has to be compiled with at least C++20 | |
| #endif | |
| // A std::pair replacement with no implicit (or any!) constructors. | |
| // All conversions are done through either member functions or static functions | |
| // Also you can use it in a range-based-for loop if the types of `first` and `second` are the same | |
| /**Usage: | |
| * | |
| * int main() { | |
| * ut::pair p1 {1, "hello"}; // p1 has type ut::pair<int, char const *> | |
| * std::printf("(%d, %s)\n", p1.first, p1.second); | |
| * | |
| * | |
| * ut::pair<double, double> p2 {1.3, 1.4}; | |
| * auto p3 = ut::fromPairLike(std::tuple {1.3, 1.4}); // p3 has type ut::pair<double, double> | |
| * assert(p2 == p3); | |
| * | |
| * for (auto d : p3) | |
| * std::printf("%f, ", d); | |
| * std::puts(""); | |
| * | |
| * auto arr = p3.to<std::array>(); // arr has type std::array<double, 2> | |
| * assert(arr == p2); | |
| * | |
| * std::pair<std::unique_ptr<int>, std::unique_ptr<std::string>> s_p | |
| * = p4.moveTo<std::pair>(); // p4 can be considered to be moved from | |
| * } | |
| * | |
| * // Output: | |
| * (1, hello) | |
| * 1.300000, 1.400000, | |
| * | |
| */ | |
| #include <iterator> | |
| #include <utility> | |
| namespace ut { | |
| template<typename Pair> | |
| concept PairLike = requires { | |
| std::tuple_size<std::decay_t<Pair>> {}; | |
| } | |
| &&std::tuple_size_v<std::decay_t<Pair>> == 2; | |
| template<typename T, typename... Args> | |
| concept PairLikeInitialisable = requires(Args... args) { | |
| { T(args...) } -> std::same_as<T>; | |
| }; | |
| template<typename T, typename... Args> | |
| concept PairLikeMoveInitialisable = requires(Args && ...args) { | |
| { T(std::move(args)...) } -> std::same_as<T>; | |
| }; | |
| template<typename T, bool is_const = false> | |
| struct pair_iterator { | |
| using value_type = T; | |
| using const_reference = value_type const &; | |
| using reference = std::conditional_t<is_const, const_reference, value_type &>; | |
| using const_pointer = value_type const *; | |
| using pointer = std::conditional_t<is_const, const_pointer, value_type *>; | |
| using iterator_category = std::bidirectional_iterator_tag; | |
| using difference_type = std::ptrdiff_t; | |
| private: | |
| template<typename First, typename Second> | |
| friend struct pair; | |
| using StorageType = pointer[2]; | |
| StorageType vals; | |
| enum { First, Second, End } pos = First; | |
| constexpr pair_iterator(pointer first, pointer second, bool is_end = false) | |
| : vals {first, second} | |
| , pos(is_end ? End : First) { } | |
| public: | |
| constexpr pair_iterator &operator++() { | |
| switch (pos) { | |
| case First: pos = Second; break; | |
| case Second: pos = End; break; | |
| case End: | |
| default:; | |
| } | |
| return *this; | |
| } | |
| constexpr bool operator==(pair_iterator const &other) const { | |
| return pos == other.pos; | |
| } | |
| constexpr reference operator*() requires(!is_const) { | |
| return *(vals[static_cast<std::size_t>(pos)]); | |
| } | |
| constexpr const_reference operator*() const { | |
| return *(vals[static_cast<std::size_t>(pos)]); | |
| } | |
| constexpr pointer operator->() requires(!is_const) { | |
| return vals[static_cast<std::size_t>(pos)]; | |
| } | |
| constexpr const_pointer operator->() const { | |
| return vals[static_cast<std::size_t>(pos)]; | |
| } | |
| constexpr pair_iterator operator++(int) { | |
| auto tmp = *this; | |
| ++(*this); | |
| return tmp; | |
| } | |
| constexpr pair_iterator &operator--() { | |
| switch (pos) { | |
| case Second: pos = First; break; | |
| case End: pos = Second; break; | |
| case First: | |
| default:; | |
| } | |
| return *this; | |
| } | |
| constexpr pair_iterator operator--(int) { | |
| auto tmp = *this; | |
| --(*this); | |
| return tmp; | |
| } | |
| }; | |
| template<typename First, typename Second> | |
| struct pair { | |
| using first_type = First; | |
| using second_type = Second; | |
| static inline constexpr auto iterable = std::same_as<first_type, second_type>; | |
| private: | |
| template<typename T> | |
| using Iter = std::conditional_t<iterable, T, void>; | |
| public: | |
| using iterator = Iter<pair_iterator<first_type, false>>; | |
| using const_iterator = Iter<pair_iterator<first_type, true>>; | |
| using reverse_iterator = std::reverse_iterator<iterator>; | |
| using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |
| first_type first {}; | |
| second_type second {}; | |
| template<template<class...> typename T> | |
| constexpr T<first_type, second_type> to() const { | |
| return {first, second}; | |
| } | |
| template<template<class...> typename T> | |
| constexpr T<first_type, second_type> moveTo() { | |
| return {std::move(first), std::move(second)}; | |
| } | |
| template<template<class, std::size_t> typename T> | |
| constexpr T<first_type, 2> to() const requires(iterable) { | |
| return {first, second}; | |
| } | |
| template<template<class, std::size_t> typename T> | |
| constexpr T<first_type, 2> moveTo() requires(iterable) { | |
| return {std::move(first), std::move(second)}; | |
| } | |
| template<PairLikeInitialisable<first_type, second_type> T> | |
| constexpr T to() const { | |
| return {first, second}; | |
| } | |
| template<PairLikeMoveInitialisable<first_type &&, second_type &&> T> | |
| constexpr T moveTo() { | |
| return {std::move(first), std::move(second)}; | |
| } | |
| // clang-format off | |
| constexpr iterator begin() requires(iterable) { return {&first, &second, false}; } | |
| constexpr const_iterator begin() const requires(iterable) { return {&first, &second, false}; } | |
| constexpr const_iterator cbegin() const requires(iterable) { return {&first, &second, false}; } | |
| constexpr iterator end() requires(iterable) { return {&first, &second, true}; } | |
| constexpr const_iterator end() const requires(iterable) { return {&first, &second, true}; } | |
| constexpr const_iterator cend() const requires(iterable) { return {&first, &second, true}; } | |
| constexpr iterator rbegin() requires(iterable) { return {&second, &first, false}; } | |
| constexpr const_iterator rbegin() const requires(iterable) { return {&second, &first, false}; } | |
| constexpr const_iterator crbegin() const requires(iterable) { return {&second, &first, false}; } | |
| constexpr iterator rend() requires(iterable) { return {&first, &second, true}; } | |
| constexpr const_iterator rend() const requires(iterable) { return {&first, &second, true}; } | |
| constexpr const_iterator crend() const requires(iterable) { return {&first, &second, true}; } | |
| // clang-format on | |
| }; | |
| template<PairLike Pair> | |
| constexpr auto fromPairLike(Pair &&t) { | |
| return pair { | |
| get<0>(t), | |
| get<1>(t), | |
| }; | |
| } | |
| template<PairLike Pair> | |
| constexpr auto fromPairLike(Pair &&t) requires(!std::is_lvalue_reference_v<Pair>) { | |
| return pair { | |
| std::move(get<0>(t)), | |
| std::move(get<1>(t)), | |
| }; | |
| } | |
| template<typename Pair> | |
| struct is_pair : std::false_type { }; | |
| template<typename First, typename Second> | |
| struct is_pair<pair<First, Second>> : std::true_type { }; | |
| template<std::size_t N, typename Pair> | |
| constexpr auto &&get(Pair &&p) requires(is_pair<std::decay_t<Pair>>::value) { | |
| static_assert(N == 0 || N == 1, "Invalid index"); | |
| if constexpr (N == 0) | |
| return p.first; | |
| else if constexpr (N == 1) | |
| return p.second; | |
| } | |
| template<PairLike This, PairLike Other> | |
| constexpr bool operator==(This const &this_, Other const &other) | |
| requires((is_pair<This>::value || is_pair<Other>::value) | |
| && std::same_as<std::tuple_element_t<0, This>, std::tuple_element_t<0, Other>> | |
| && std::same_as<std::tuple_element_t<1, This>, std::tuple_element_t<1, Other>>) { | |
| auto const &[f_t, s_t] = this_; | |
| auto const &[f_o, s_o] = other; | |
| return f_t == f_o && s_t == s_o; | |
| } | |
| template<typename First, typename Second> | |
| pair(First, Second) -> pair<First, Second>; | |
| } // namespace ut | |
| template<typename First, typename Second> | |
| struct std::tuple_size<ut::pair<First, Second>> : public integral_constant<std::size_t, 2> { }; | |
| template<std::size_t N, typename First, typename Second> | |
| struct std::tuple_element<N, ut::pair<First, Second>> { | |
| static_assert(N == 0 || N == 1, "Invalid index"); | |
| }; | |
| template<typename First, typename Second> | |
| struct std::tuple_element<0, ut::pair<First, Second>> { | |
| using type = First; | |
| }; | |
| template<typename First, typename Second> | |
| struct std::tuple_element<1, ut::pair<First, Second>> { | |
| using type = Second; | |
| }; | |
| /* | |
| MIT License | |
| Copyright (c) 2024 dk949 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| */ | |
| #endif // UT_PAIR_HPP |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| a better `std::pair` implementation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment