/* *****************************************************************************
* kblib is a general utility library for C++14 and C++17, intended to provide
* performant high-level abstractions and more expressive ways to do simple
* things.
*
* Copyright (c) 2021 killerbee
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
* ****************************************************************************/
/**
* @file
* @brief Provides delayed_construct, an optional-like type that cannot be
* cleared.
*
* @author killerbee
* @date 2019-2021
* @copyright GNU General Public Licence v3.0
*/
#ifndef DELAYED_CONSTRUCT_H
#define DELAYED_CONSTRUCT_H
#include "hash.h"
#include "tdecl.h"
#if KBLIB_USE_CXX17
# include
# if KBLIB_USE_CXX20
# include
# endif
namespace KBLIB_NS {
template
class delayed_construct {
private:
using Base = std::optional;
protected:
Base storage;
public:
template
KBLIB_CXX20(requires(std::constructible_from))
delayed_construct(Ts&&... args)
: storage(std::forward(args)...) {}
template , int> = 0>
auto operator=(U&& t) -> delayed_construct& {
storage = std::forward(t);
return *this;
}
auto operator=(std::nullopt_t) -> delayed_construct& = delete;
template
auto emplace(Ts&&... args) const -> decltype(auto) {
return storage.emplace(std::forward(args)...);
}
delayed_construct(const delayed_construct&) = default;
delayed_construct(delayed_construct&&) = default;
auto operator=(const delayed_construct&) -> delayed_construct& = default;
auto operator=(delayed_construct&&) -> delayed_construct& = default;
~delayed_construct() = default;
auto operator->() const -> decltype(auto) { return storage.operator->(); }
auto operator*() const -> decltype(auto) { return storage.operator*(); }
auto value() const -> decltype(auto) { return storage.value(); }
explicit operator bool() const { return static_cast(storage); }
KBLIB_NODISCARD constexpr auto is_constructed() const noexcept -> bool {
return storage.has_value();
}
// TODO(killerbee13): add C++20 operator<=> support to delayed_construct
# if 0
template U>
requires(not std::same_as) KBLIB_NODISCARD auto
operator<=>(const U& other) const {
return storage <=> other;
}
template U>
requires(not std::same_as) KBLIB_NODISCARD auto
operator==(const U& other) const {
return storage == other;
}
KBLIB_NODISCARD auto operator<=>(
const delayed_construct& other) const = default;
KBLIB_NODISCARD auto operator==(const delayed_construct& other) const -> bool
= default;
# else
# define OVERLOAD_DEFER_OP(op) \
KBLIB_NODISCARD friend constexpr auto operator op( \
const delayed_construct& lhs, \
const delayed_construct& rhs) noexcept->bool { \
return lhs.storage op rhs.storage; \
} \
template \
KBLIB_NODISCARD friend constexpr auto operator op( \
const delayed_construct& lhs, \
const delayed_construct& rhs) noexcept->bool { \
return lhs.storage op static_cast&>(rhs); \
} \
KBLIB_NODISCARD friend constexpr auto operator op( \
const delayed_construct& lhs, \
std::nullopt_t rhs) noexcept->bool { \
return lhs.storage op rhs; \
} \
KBLIB_NODISCARD friend constexpr auto operator op( \
std::nullopt_t lhs, \
const delayed_construct& rhs) noexcept->bool { \
return lhs op rhs.storage; \
} \
template \
KBLIB_NODISCARD friend constexpr auto operator op( \
const delayed_construct& opt, const U& value) noexcept->bool { \
return opt.storage op value; \
} \
template \
KBLIB_NODISCARD friend constexpr auto operator op( \
const U& value, const delayed_construct& opt) noexcept->bool { \
return value op opt.storage; \
}
/**
* @name Equality
* @brief Two delayed_construct objects are equal if either neither
* contains a value, or if both contain the same value. std::nullopt_t is
* equivalent to a non-constructed object, and a value is equivalent to a
* constructed one.
*/
///@{
OVERLOAD_DEFER_OP(==)
OVERLOAD_DEFER_OP(!=)
///@}
/**
* @name Comparison
* @brief A non-constructed delayed_construct object is less than any
* constructed one. std::nullopt_t is equivalent to a non-constructed object,
* and a value is equivalent to a constructed one.
*/
///@{
OVERLOAD_DEFER_OP(<)
OVERLOAD_DEFER_OP(<=)
OVERLOAD_DEFER_OP(>)
OVERLOAD_DEFER_OP(>=)
///@}
# undef OVERLOAD_DEFER_OP
# endif
friend struct std::hash>;
friend struct FNV_hash>;
};
template
struct FNV_hash, void> {
KBLIB_NODISCARD constexpr std::size_t operator()(
const delayed_construct& key,
std::size_t offset
= fnv::fnv_offset::value) const noexcept {
if (key) {
return FNV_hash{}(key.value(), offset);
} else {
return FNV_hash{}(std::nullopt, offset);
}
}
};
} // namespace KBLIB_NS
namespace std {
// Inheriting from another hash specialization means that this hash is (mostly)
// disabled when std::hash is (if I read the requirements correctly,
// operator() should not be declared in a disabled std::hash, but I basically
// can't do that in any sensible way)
template
struct hash> : hash {
using argument_type = kblib::delayed_construct;
auto operator()(const argument_type& value) const noexcept -> std::size_t {
return hash>{}(static_cast&>(value));
}
};
} // namespace std
#endif // KBLIB_USE_CXX17
#endif // DELAYED_CONSTRUCT_H