Skip to content

Instantly share code, notes, and snippets.

@Qyriad
Last active June 27, 2024 21:10
Show Gist options
  • Select an option

  • Save Qyriad/68c9a7464a9e326f3b80c7ea8dcdc443 to your computer and use it in GitHub Desktop.

Select an option

Save Qyriad/68c9a7464a9e326f3b80c7ea8dcdc443 to your computer and use it in GitHub Desktop.
C++ non-nullable pointer wrapper specifically for out parameters
#pragma once
#include <memory> // std::addressof
#include <optional>
#include <type_traits> // std::type_identity_t
template<typename T>
using NoDeduce = std::type_identity_t<T>;
/** Non-nullable pointer, for making out-parameters explicit.
* Unlike nix::ref, this neither enforces nor assumes any particular ownership semantics,
* making it safe to use on things like references from dereferencing an std::unique_ptr.
*/
template<typename T>
class RefMut
{
using Self = RefMut<T>;
/** Changing *what* this points to is not allowed, as it would always be semantically
* incorrect.
*/
T *const inner;
public:
constexpr explicit RefMut(T &value) noexcept
: inner(std::addressof(value))
{
// We use std::addressof() because T could have overloaded operator&. *sigh*.
}
constexpr inline T &operator*() noexcept
{
return *this->inner;
}
constexpr inline T *operator->() noexcept
{
return this->inner;
}
constexpr inline T &get() const noexcept
{
return *this->inner;
}
/** Returns the inner pointer, by value.
*
* Mutating the inner pointer (that is, changing *what* it points to), is not
* allowed in this class as that behavior would always be incorrect.
*/
// No I will not overload operator&, fuck you.
constexpr inline T *ptr() const noexcept
{
return this->inner;
}
/** Default construction is disallowed, as it would always be semantically incorrect.
*/
RefMut() = delete;
/** operator= is disallowed, as its not obvious what it would do.
* Use *foo = bar instead.
*/
Self operator=(Self const &rhs) = delete;
/** Move assignment is disallowed, as it wwould always be semantically incorrect.
* This class does not and cannot ever own a value.
*/
Self operator=(Self &&rhs) = delete;
/** Copy construction is perfectly fine and does not deep copy, allowing functions to
* take RefMut<T> by value like they can with shared_ptr<T>.
*/
constexpr RefMut(Self const &other) noexcept
: inner(other.inner)
{ }
/** Move construction is allowed but identical to copy construction.
*/
constexpr RefMut(Self &&other) noexcept
: inner(other.inner)
{ }
// Disable template type dedudction for CastTargetT, requring this function
// to always require its template type argument to be specified, like
// dynamic_cast<T> does.
// e.g., RefMut<Bar> bar = foo.dynamic_cast() is disallowed;
// like with dynamic_cast, you should write
// auto bar = foo.dynamic_pointer_cast<Bar>()
template<typename CastTargetT>
constexpr std::optional<RefMut<NoDeduce<CastTargetT>>> dynamic_pointer_cast()
const noexcept
{
CastTargetT const *raw_casted = dynamic_cast<CastTargetT *>(this->inner);
if (raw_casted == nullptr) {
return std::nullopt;
}
return RefMut<CastTargetT>(*raw_casted);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment