Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions phlex/model/identifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <variant>

namespace phlex::experimental {
/// If you're comparing to an identifier you know at compile time, you're probably not going to need
Expand Down Expand Up @@ -95,6 +97,115 @@ namespace phlex::experimental {
// Really trying to avoid the extra function call here
inline std::string_view format_as(identifier const& id) { return std::string_view(id); }
inline std::size_t hash_value(identifier const& id) { return id.hash(); }

/// The explicit representation of a meaningful empty textual value (for example, an empty
/// product suffix). It is distinct from an omitted value (`std::nullopt` in a selector) and
/// from any named `identifier`.
struct empty_string {
constexpr bool operator==(empty_string const&) const noexcept = default;
constexpr std::strong_ordering operator<=>(empty_string const&) const noexcept = default;
};

/// A value that is either `empty_string` or a named `identifier`.
///
/// Value states:
/// - empty: holds `empty_string`; `text()` and formatting produce `""`.
/// - named: holds an `identifier`; `text()` and formatting produce the identifier's text.
///
/// Value semantics:
/// - Equality holds only between two empty values or two named values with equal text.
/// An empty value never equals a named value, even one whose text is empty.
/// - Ordering places every empty value before every named value; named values are ordered by
/// `identifier`'s ordering.
/// - Hashing gives all empty values the same hash, and named values the hash of their
/// identifier.
///
/// The default constructor produces the empty state.
///
/// The lasting textual construction policy mirrors `identifier`: C strings convert implicitly,
/// while `std::string` and `std::string_view` require explicit construction. Empty text produces
/// `empty_string`; non-empty text is passed to `identifier`, including its validation when enabled.
/// C strings must be non-null and null-terminated; only text before the first NUL is consumed.
/// Length-bearing inputs retain their full length and are never routed through a C-string boundary.
class PHLEX_MODEL_EXPORT identifier_or_empty {
public:
identifier_or_empty() noexcept = default;

// NOLINTNEXTLINE(google-explicit-constructor) - Implicit conversion is intentional
identifier_or_empty(empty_string) noexcept : value_{empty_string{}} {}

// NOLINTNEXTLINE(google-explicit-constructor) - Implicit conversion is intentional
identifier_or_empty(identifier const& id) : value_{id} {}

// NOLINTNEXTLINE(google-explicit-constructor) - Implicit conversion is intentional
identifier_or_empty(identifier&& id) noexcept : value_{std::move(id)} {}

explicit identifier_or_empty(std::string_view str)
{
if (!str.empty()) {
value_.emplace<identifier>(str);
}
}

explicit identifier_or_empty(std::string const& str) :
identifier_or_empty(std::string_view{str})
{
}

// NOLINTNEXTLINE(google-explicit-constructor) - Implicit conversion is intentional
identifier_or_empty(char const* lit) : identifier_or_empty(std::string_view{lit}) {}

/// True when this value holds `empty_string`.
bool empty() const noexcept { return std::holds_alternative<empty_string>(value_); }

/// Textual view of the value: `""` for the empty state, the identifier's text otherwise.
std::string_view text() const noexcept
{
if (auto const* id = std::get_if<identifier>(&value_)) {
return std::string_view(*id);
}
return {};
}

/// The named identifier, or nullptr when this value is empty.
identifier const* named() const noexcept { return std::get_if<identifier>(&value_); }

/// Hash of the value; all empty values share the hash `0`.
std::size_t hash() const noexcept
{
if (auto const* id = std::get_if<identifier>(&value_)) {
return id->hash();
}
return 0;
}

bool operator==(identifier_or_empty const& rhs) const noexcept { return value_ == rhs.value_; }

std::strong_ordering operator<=>(identifier_or_empty const& rhs) const noexcept
{
if (value_.index() != rhs.value_.index()) {
return value_.index() <=> rhs.value_.index();
}
if (auto const* id = std::get_if<identifier>(&value_)) {
return *id <=> std::get<identifier>(rhs.value_);
}
return std::strong_ordering::equal;
}

private:
// Index 0 is the empty state, so empty values order before named values.
std::variant<empty_string, identifier> value_{empty_string{}};
};

inline std::string_view format_as(empty_string) { return {}; }
inline std::string_view format_as(identifier_or_empty const& value) { return value.text(); }
inline std::size_t hash_value(identifier_or_empty const& value) { return value.hash(); }

/// The domain type of a product suffix. It is an alias of `identifier_or_empty` and has the
/// same value states, equality, ordering, formatting, and hashing: `empty_string` is an
/// explicit empty suffix, and a named identifier is a valid suffix name. It is not an
/// omitted selection criterion; that is `std::nullopt` in a product selector.
using suffix_t = identifier_or_empty;
}

template <>
Expand All @@ -105,4 +216,12 @@ struct std::hash<phlex::experimental::identifier> {
}
};

template <>
struct std::hash<phlex::experimental::identifier_or_empty> {
std::size_t operator()(phlex::experimental::identifier_or_empty const& value) const noexcept
{
return value.hash();
}
};

#endif // PHLEX_MODEL_IDENTIFIER_HPP
Loading
Loading