Skip to content

Commit

Permalink
Containers: new Pair class.
Browse files Browse the repository at this point in the history
Yeh, I got fed up with std::pair limitations.
  • Loading branch information
mosra committed Jun 15, 2021
1 parent 51ee6e9 commit e9d43fa
Show file tree
Hide file tree
Showing 11 changed files with 1,292 additions and 1 deletion.
2 changes: 2 additions & 0 deletions doc/corrade-changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ namespace Corrade {
[mosra/corrade#117](https://github.com/mosra/corrade/pull/117).
- New @ref Containers::BigEnumSet class for storing enum sets with more than
64 values
- New @ref Containers::Pair class that fixes various issues and pitfalls of
@ref std::pair
- New @ref Containers::String class as a lightweight but more flexible
alternative to @ref std::string
- New @ref Containers::BasicStringView "Containers::StringView" class as a
Expand Down
15 changes: 15 additions & 0 deletions doc/snippets/Containers-stl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "Corrade/Containers/Array.h"
#include "Corrade/Containers/ArrayViewStl.h"
#include "Corrade/Containers/PairStl.h"
#include "Corrade/Containers/PointerStl.h"
#include "Corrade/Containers/StringStl.h"
#include "Corrade/Containers/ReferenceStl.h"
Expand All @@ -54,6 +55,20 @@ auto d = Containers::array<int>({5}); // d.size() == 1, d[0] == 5
/* [Array-initializer-list] */
}

{
/* [Pair] */
std::pair<float, int> a{35.0f, 7};
Containers::Pair<float, int> b{a};

std::pair<bool, int*> c(Containers::pair(false, &b.second()));

auto d = Containers::pair(std::pair<char, double>{'p', 3.14});
// d is Containers::pair<char, double>
/* [Pair] */
static_cast<void>(c);
static_cast<void>(d);
}

{
/* [Pointer] */
std::unique_ptr<int> a{new int{5}};
Expand Down
10 changes: 10 additions & 0 deletions doc/snippets/Containers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "Corrade/Containers/EnumSet.hpp"
#include "Corrade/Containers/LinkedList.h"
#include "Corrade/Containers/Optional.h"
#include "Corrade/Containers/Pair.h"
#include "Corrade/Containers/Pointer.h"
#include "Corrade/Containers/Reference.h"
#include "Corrade/Containers/ScopeGuard.h"
Expand Down Expand Up @@ -738,6 +739,15 @@ auto b = Containers::optional<std::string>('a', 'b');
/* [optional-inplace] */
}

{
/* [pair] */
auto a = Containers::Pair<float, int>{35.0f, 7};
auto b = Containers::pair(35.0f, 7);
/* [pair] */
static_cast<void>(a);
static_cast<void>(b);
}

{
/* [pointer] */
std::string* ptr = DOXYGEN_IGNORE({});
Expand Down
2 changes: 2 additions & 0 deletions src/Corrade/Containers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ set(CorradeContainers_HEADERS
MoveReference.h
Optional.h
OptionalStl.h
Pair.h
PairStl.h
Pointer.h
PointerStl.h
Reference.h
Expand Down
1 change: 1 addition & 0 deletions src/Corrade/Containers/Containers.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ template<class> class LinkedList;
template<class Derived, class List = LinkedList<Derived>> class LinkedListItem;

template<class> class Optional;
template<class, class> class Pair;
template<class> class Pointer;
template<class> class Reference;
template<class> class MoveReference;
Expand Down
3 changes: 2 additions & 1 deletion src/Corrade/Containers/Optional.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ template<class T> class Optional {
#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ > 4
/** @overload */
/* This causes ambiguous overload on GCC 4.8 (and I assume 4.9 as
well), so disabling it there. See also the corresponding test. */
well), so disabling it there. See also the corresponding test, same
is in Pair. */
const T&& operator*() const && {
CORRADE_ASSERT(_set, "Containers::Optional: the optional is empty", Utility::move(_value));
return Utility::move(_value);
Expand Down
252 changes: 252 additions & 0 deletions src/Corrade/Containers/Pair.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#ifndef Corrade_Containers_Pair_h
#define Corrade_Containers_Pair_h
/*
This file is part of Corrade.
Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
2017, 2018, 2019, 2020, 2021
Vladimír Vondruš <[email protected]>
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.
*/

#include <type_traits>

#include "Corrade/Tags.h"
#ifndef CORRADE_NO_DEBUG
#include "Corrade/Utility/Debug.h"
#endif
#include "Corrade/Utility/Move.h"

/** @file
* @brief Class @ref Corrade::Containers::Pair
* @m_since_latest
*/

namespace Corrade { namespace Containers {

namespace Implementation {
template<class, class, class> struct PairConverter;
}

/**
@brief Pair of values
@m_since_latest
An alternative to @ref std::pair that is trivially copyable for trivial types,
provides move semantics consistent across standard library implementations and
guarantees usability in @cpp constexpr @ce contexts even in C++11. On the other
hand, to simplify both the implementation and usage semantics, the type doesn't
support references --- wrap them in a @ref Reference in order to store them in
a @ref Pair. Such type composition allows you to both rebind the reference and
update the referenced value and the intent is clear.
Similarly to other containers and equivalently to @ref std::make_pair(),
there's also @ref pair(). The following two lines are equivalent:
@snippet Containers.cpp pair
Unlike with @ref std::pair, access to the pair elements is done using
@ref first() and @ref second() member *functions*, without providing access to
the members directly. This is done in order to future-proof the design and have
extra flexibility in how the internals are defined.
@section Containers-Pair-stl STL compatibility
Instances of @ref Pair are *explicitly* copy- and move-convertible to and
from @ref std::pair if you include @ref Corrade/Containers/PairStl.h. The
conversion is provided in a separate header to avoid overhead from an
unconditional @cpp #include <utility> @ce. Additionally, the @ref pair(T&&)
overload also allows for such a conversion. Example:
@snippet Containers-stl.cpp Pair
@see @ref pair(F&&, S&&), @ref pair(T&&)
*/
template<class F, class S> class Pair {
static_assert(!std::is_lvalue_reference<F>::value && !std::is_lvalue_reference<S>::value, "use a Reference<T> to store a T& in a Pair");

public:
typedef F FirstType; /**< @brief First type */
typedef S SecondType; /**< @brief Second type */

/**
* @brief Construct a default-initialized pair
*
* Trivial types are not initialized, default constructor called
* otherwise. Because of the differing behavior for trivial types it's
* better to explicitly use either the @ref Pair(ValueInitT) or the
* @ref Pair(NoInitT) variant instead.
* @see @ref DefaultInit, @ref std::is_trivial
*/
constexpr explicit Pair(Corrade::DefaultInitT) noexcept(std::is_nothrow_constructible<F>::value && std::is_nothrow_constructible<S>::value) {}

/**
* @brief Construct a value-initialized pair
*
* Trivial types are zero-initialized, default constructor called
* otherwise. This is the same as @ref Pair().
* @see @ref ValueInit, @ref Pair(DefaultInitT)
*/
constexpr explicit Pair(Corrade::ValueInitT) noexcept(std::is_nothrow_constructible<F>::value && std::is_nothrow_constructible<S>::value): _first{}, _second{} {}

#ifdef DOXYGEN_GENERATING_OUTPUT
/**
* @brief Construct a pair without initializing its contents
*
* Enabled only for trivial types and types that implement the
* @ref NoInit constructor. The contents are *not* initialized. Useful
* if you will be overwriting both members later anyway or if you need
* to initialize in a way that's not expressible via any other
* @ref Pair constructor.
*
* For trivial types is equivalent to @ref Pair(DefaultInitT).
*/
explicit Pair(Corrade::NoInitT) noexcept;
#else
template<class F_ = F, class = typename std::enable_if<std::is_standard_layout<F_>::value && std::is_standard_layout<S>::value && std::is_trivial<F_>::value && std::is_trivial<S>::value>::type> explicit Pair(Corrade::NoInitT) noexcept {}
template<class F_ = F, class S_ = S, class = typename std::enable_if<std::is_constructible<F_, Corrade::NoInitT>::value && std::is_constructible<S_, Corrade::NoInitT>::value>::type> explicit Pair(Corrade::NoInitT) noexcept: _first{Corrade::NoInit}, _second{Corrade::NoInit} {}
#endif

/**
* @brief Default constructor
*
* Alias to @ref Pair(ValueInitT).
*/
constexpr /*implicit*/ Pair() noexcept(std::is_nothrow_constructible<F>::value && std::is_nothrow_constructible<S>::value): Pair{Corrade::ValueInit} {}

/**
* @brief Constructor
*
* @see @ref pair(F&&, S&&)
*/
constexpr /*implicit*/ Pair(const F& first, const S& second) noexcept(std::is_nothrow_copy_constructible<F>::value && std::is_nothrow_copy_constructible<S>::value): _first{first}, _second{second} {}
/** @overload */
constexpr /*implicit*/ Pair(const F& first, S&& second) noexcept(std::is_nothrow_copy_constructible<F>::value && std::is_nothrow_move_constructible<S>::value): _first{first}, _second{Utility::move(second)} {}
/** @overload */
constexpr /*implicit*/ Pair(F&& first, const S& second) noexcept(std::is_nothrow_move_constructible<F>::value && std::is_nothrow_copy_constructible<S>::value): _first{Utility::move(first)}, _second{second} {}
/** @overload */
constexpr /*implicit*/ Pair(F&& first, S&& second) noexcept(std::is_nothrow_move_constructible<F>::value && std::is_nothrow_move_constructible<S>::value): _first{Utility::move(first)}, _second{Utility::move(second)} {}

/**
* @brief Copy-construct a pair from external representation
*
* @see @ref Containers-Pair-stl, @ref pair(T&&)
*/
template<class T, class = decltype(Implementation::PairConverter<F, S, T>::from(std::declval<const T&>()))> explicit Pair(const T& other) noexcept(std::is_nothrow_copy_constructible<F>::value && std::is_nothrow_copy_constructible<S>::value): Pair{Implementation::PairConverter<F, S, T>::from(other)} {}

/**
* @brief Move-construct a pair from external representation
*
* @see @ref Containers-Pair-stl, @ref pair(T&&)
*/
template<class T, class = decltype(Implementation::PairConverter<F, S, T>::from(std::declval<T&&>()))> explicit Pair(T&& other) noexcept(std::is_nothrow_move_constructible<F>::value && std::is_nothrow_move_constructible<S>::value): Pair{Implementation::PairConverter<F, S, T>::from(Utility::move(other))} {}

/**
* @brief Copy-convert the pair to external representation
*
* @see @ref Containers-Pair-stl
*/
template<class T, class = decltype(Implementation::PairConverter<F, S, T>::to(std::declval<const Pair<F, S>&>()))> explicit operator T() const & {
return Implementation::PairConverter<F, S, T>::to(*this);
}

/**
* @brief Move-convert the pair to external representation
*
* @see @ref Containers-Pair-stl
*/
template<class T, class = decltype(Implementation::PairConverter<F, S, T>::to(std::declval<Pair<F, S>&&>()))> explicit operator T() && {
return Implementation::PairConverter<F, S, T>::to(Utility::move(*this));
}

/** @brief Equality comparison */
constexpr bool operator==(const Pair<F, S>& other) const {
return _first == other._first && _second == other._second;
}

/** @brief Non-equality comparison */
constexpr bool operator!=(const Pair<F, S>& other) const {
return !operator==(other);
}

/** @brief First element */
F& first() & { return _first; }
F&& first() && { return Utility::move(_first); } /**< @overload */
constexpr const F& first() const & { return _first; } /**< @overload */
#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ > 4
/** @overload */
/* This causes ambiguous overload on GCC 4.8 (and I assume 4.9 as
well), so disabling it there. See also the corresponding test, same
is in Optional. */
constexpr const F&& first() const && { return Utility::move(_first); }
#endif

/** @brief Second element */
S& second() & { return _second; }
S&& second() && { return Utility::move(_second); } /**< @overload */
constexpr const S& second() const & { return _second; } /**< @overload */
#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ > 4
/** @overload */
/* This causes ambiguous overload on GCC 4.8 (and I assume 4.9 as
well), so disabling it there. See also the corresponding test, same
is in Optional. */
constexpr const S&& second() const && { return Utility::move(_second); }
#endif

private:
F _first;
S _second;
};

/** @relatesalso Pair
@brief Make a pair
Convernience alternative to @ref Pair::Pair(const F&, const S&) and overloads.
The following two lines are equivalent:
@snippet Containers.cpp pair
*/
template<class F, class S> constexpr Pair<typename std::decay<F>::type, typename std::decay<S>::type> pair(F&& first, S&& second) {
return Pair<typename std::decay<F>::type, typename std::decay<S>::type>{Utility::forward<F>(first), Utility::forward<S>(second)};
}

namespace Implementation {
template<class> struct DeducedPairConverter;
}

/** @relatesalso Pair
@brief Make a pair from external representation
@see @ref Containers-Pair-stl
*/
template<class T> inline auto pair(T&& other) -> decltype(Implementation::DeducedPairConverter<typename std::decay<T>::type>::from(Utility::forward<T>(other))) {
return Implementation::DeducedPairConverter<typename std::decay<T>::type>::from(Utility::forward<T>(other));
}

#ifndef CORRADE_NO_DEBUG
/** @debugoperator{Pair} */
template<class F, class S> Utility::Debug& operator<<(Utility::Debug& debug, const Pair<F, S>& value) {
return debug << "{" << Utility::Debug::nospace << value.first() << Utility::Debug::nospace << "," << value.second() << Utility::Debug::nospace << "}";
}
#endif

}}

#endif
Loading

0 comments on commit e9d43fa

Please sign in to comment.