#ifndef RFL_NAMEDTUPLE_HPP_ #define RFL_NAMEDTUPLE_HPP_ #include #include #include #include #include "Field.hpp" #include "Literal.hpp" #include "Tuple.hpp" #include "apply.hpp" #include "get.hpp" #include "internal/StringLiteral.hpp" #include "internal/find_index.hpp" #include "internal/is_extra_fields.hpp" #include "make_from_tuple.hpp" #include "tuple_cat.hpp" namespace rfl { /// A named tuple behaves like a tuple, /// but the fields have explicit names, which /// allows for reflection. /// IMPORTANT: We have two template specializations. One with fields, one /// without fields. template class NamedTuple; // ---------------------------------------------------------------------------- template class NamedTuple { template struct Index {}; static constexpr auto seq_ = std::make_integer_sequence(); public: using Fields = rfl::Tuple...>; using Names = Literal::name_...>; using Values = rfl::Tuple::Type...>; public: /// Construct from the values. NamedTuple(typename std::remove_cvref::type::Type&&... _values) : values_( std::forward::type::Type>( _values)...) {} /// Construct from the values. NamedTuple( const typename std::remove_cvref::type::Type&... _values) : values_(rfl::make_tuple(_values...)) {} /// Construct from the fields. NamedTuple(FieldTypes&&... _fields) : values_(rfl::make_tuple(std::move(_fields.value_)...)) {} /// Construct from the fields. NamedTuple(const FieldTypes&... _fields) : values_(rfl::make_tuple(_fields.value_...)) {} /// Construct from a tuple containing fields. NamedTuple(rfl::Tuple&& _tup) : NamedTuple(rfl::make_from_tuple>( std::forward>(_tup))) {} /// Construct from a tuple containing fields. NamedTuple(const rfl::Tuple& _tup) : NamedTuple(rfl::make_from_tuple>(_tup)) {} /// Copy constructor. NamedTuple(const NamedTuple& _other) = default; /// Move constructor. NamedTuple(NamedTuple&& _other) = default; /// Copy constructor. template NamedTuple(const NamedTuple& _other) : NamedTuple(retrieve_fields(_other.fields(), seq_)) {} /// Move constructor. template NamedTuple(NamedTuple&& _other) : NamedTuple(retrieve_fields( std::forward>(_other).fields(), seq_)) {} ~NamedTuple() = default; /// Returns a new named tuple with additional fields. template auto add(Field<_name, FType>&& _head, Tail&&... _tail) && { using Head = Field<_name, FType>; if constexpr (sizeof...(Tail) > 0) { return NamedTuple>( std::move(*this).make_fields(seq_, std::forward(_head))) .add(std::forward(_tail)...); } else { return NamedTuple>( std::move(*this).make_fields(seq_, std::forward(_head))); } } /// Returns a new named tuple with additional fields. template auto add(Field<_name, FType> _head, const Tail&... _tail) const& { using Head = Field<_name, FType>; if constexpr (sizeof...(Tail) > 0) { return NamedTuple>( make_fields(seq_, _head)) .add(_tail...); } else { return NamedTuple>( make_fields(seq_, _head)); } } /// Template specialization for rfl::Tuple, so we can pass fields from other /// named tuples. template auto add(rfl::Tuple&& _tuple, Tail&&... _tail) && { if constexpr (sizeof...(Tail) > 0) { return std::move(*this) .add_tuple(std::forward>(_tuple)) .add(std::forward(_tail)...); } else { return std::move(*this).add_tuple( std::forward>(_tuple)); } } /// Template specialization for rfl::Tuple, so we can pass fields from other /// named tuples. template auto add(rfl::Tuple _tuple, const Tail&... _tail) const& { if constexpr (sizeof...(Tail) > 0) { return add_tuple(std::move(_tuple)).add(_tail...); } else { return add_tuple(std::move(_tuple)); } } /// Template specialization for NamedTuple, so we can pass fields from other /// named tuples. template auto add(NamedTuple&& _named_tuple, Tail&&... _tail) && { return std::move(*this).add( std::forward>( std::forward>(_named_tuple).fields()), std::forward(_tail)...); } /// Template specialization for NamedTuple, so we can pass fields from other /// named tuples. template auto add(NamedTuple _named_tuple, const Tail&... _tail) const& { return add(_named_tuple.fields(), _tail...); } /// Creates a new named tuple by applying the supplied function to /// field. The function is expected to return a named tuple itself. template auto and_then(const F& _f) && { const auto transform_field = [&_f](auto... _fields) { return rfl::tuple_cat(_f(std::move(_fields)).fields()...); }; const auto to_nt = [](rfl::Tuple&& _tup) { return NamedTuple(_tup); }; auto new_fields = rfl::apply(transform_field, std::move(*this).fields()); return to_nt(std::move(new_fields)); } /// Creates a new named tuple by applying the supplied function to /// field. The function is expected to return a named tuple itself. template auto and_then(const F& _f) const& { const auto transform_field = [&_f](auto... _fields) { return rfl::tuple_cat(_f(std::move(_fields)).fields()...); }; const auto to_nt = [](rfl::Tuple&& _tup) { return NamedTuple(_tup); }; auto new_fields = rfl::apply(transform_field, std::move(fields())); return to_nt(std::move(new_fields)); } /// Invokes a callable object once for each field in order. template void apply(F&& _f) const& { const auto apply_to_field = [&_f](const auto&... fields) { ((_f(fields)), ...); }; rfl::apply(apply_to_field, fields()); } /// Returns a tuple containing the fields. Fields fields() && { return std::move(*this).make_fields(seq_); } /// Returns a tuple containing the fields. Fields fields() const& { return make_fields(seq_); } /// Gets a field by index. template auto& get() { return rfl::get<_index>(*this); } /// Gets a field by name. template auto& get() { return rfl::get<_field_name>(*this); } /// Gets a field by the field type. template auto& get() { return rfl::get(*this); } /// Gets a field by index. template const auto& get() const { return rfl::get<_index>(*this); } /// Gets a field by name. template const auto& get() const { return rfl::get<_field_name>(*this); } /// Gets a field by the field type. template const auto& get() const { return rfl::get(*this); } /// Returns the results wrapped in a field. template auto get_field() const { return rfl::make_field<_field_name>(rfl::get<_field_name>(*this)); } /// Copy assignment operator. NamedTuple& operator=( const NamedTuple& _other) = default; /// Move assignment operator. NamedTuple& operator=( NamedTuple&& _other) noexcept = default; /// Equality operator inline auto operator==(const rfl::NamedTuple& _other) const { return values() == _other.values(); } /// Three-way comparison operator. inline auto operator<=>(const rfl::NamedTuple& _other) const { return values() <=> _other.values(); } /// Returns the number of fields. Note that this is not necessary the same /// thing as .size(), because there might be rfl::ExtraFields, which are /// simply counted as one entry by .size(), but are counted by individually by /// .num_fields(). size_t num_fields() const { if constexpr (pos_extra_fields() == -1) { return size(); } else { return calc_num_fields(); } } /// The position of the extra fields, or -1 if there aren't any. constexpr static int pos_extra_fields() { return pos_extra_fields_; } /// Replaces one or several fields, returning a new version /// with the non-replaced fields left unchanged. template auto replace(Field<_name, FType>&& _field, OtherRFields&&... _other_fields) && { using RField = Field<_name, FType>; constexpr auto num_other_fields = sizeof...(OtherRFields); if constexpr (num_other_fields == 0) { return std::move(*this).template replace_value(_field.value_); } else { return std::move(*this) .template replace_value(_field.value_) .replace(std::forward(_other_fields)...); } } /// Replaces one or several fields, returning a new version /// with the non-replaced fields left unchanged. template auto replace(Field<_name, FType> _field, const OtherRFields&... _other_fields) const& { using RField = Field<_name, FType>; constexpr auto num_other_fields = sizeof...(OtherRFields); if constexpr (num_other_fields == 0) { return replace_value(std::move(_field.value_)); } else { return replace_value(std::move(_field.value_)) .replace(_other_fields...); } } /// Template specialization for rfl::Tuple, so we can pass fields from other /// named tuples. template auto replace(rfl::Tuple&& _tuple, Tail&&... _tail) && { if constexpr (sizeof...(Tail) > 0) { return std::move(*this) .replace_tuple(std::forward>(_tuple)) .replace(std::forward(_tail)...); } else { return std::move(*this).replace_tuple( std::forward>(_tuple)); } } /// Template specialization for rfl::Tuple, so we can pass fields from other /// named tuples. template auto replace(rfl::Tuple _tuple, const Tail&... _tail) const& { if constexpr (sizeof...(Tail) > 0) { return replace_tuple(std::move(_tuple)).replace(_tail...); } else { return replace_tuple(std::move(_tuple)); } } /// Template specialization for NamedTuple, so we can pass fields from other /// named tuples. template auto replace(NamedTuple&& _named_tuple, Tail&&... _tail) && { return std::move(*this).replace( std::forward>(_named_tuple).fields(), std::forward(_tail)...); } /// Template specialization for NamedTuple, so we can pass fields from other /// named tuples. template auto replace(NamedTuple _named_tuple, const Tail&... _tail) const& { return replace(_named_tuple.fields(), _tail...); } /// Returns the size of the named tuple static constexpr size_t size() { return rfl::tuple_size_v; } /// Creates a new named tuple by applying the supplied function to every /// field. template auto transform(const F& _f) && { const auto transform_field = [&_f](auto... fields) { return rfl::make_tuple(_f(std::move(fields))...); }; const auto to_nt = [](rfl::Tuple&& _tup) { return NamedTuple(_tup); }; auto new_fields = rfl::apply(transform_field, std::move(*this).fields()); return to_nt(std::move(new_fields)); } /// Creates a new named tuple by applying the supplied function to every /// field. template auto transform(const F& _f) const& { const auto transform_field = [&_f](auto... fields) { return rfl::make_tuple(_f(std::move(fields))...); }; const auto to_nt = [](rfl::Tuple&& _tup) { return NamedTuple(_tup); }; auto new_fields = rfl::apply(transform_field, std::move(fields())); return to_nt(std::move(new_fields)); } /// Returns the underlying rfl::Tuple. Values& values() { return values_; } /// Returns the underlying rfl::Tuple. const Values& values() const { return values_; } private: /// Adds the elements of a tuple to a newly created named tuple, /// and other elements to a newly created named tuple. template constexpr auto add_tuple(rfl::Tuple&& _tuple) && { const auto a = [this](auto&&... _fields) { return std::move(*this).add(std::forward(_fields)...); }; return rfl::apply(a, std::forward>(_tuple)); } /// Adds the elements of a tuple to a newly created named tuple, /// and other elements to a newly created named tuple. template constexpr auto add_tuple(rfl::Tuple&& _tuple) const& { const auto a = [this](auto&&... _fields) { return this->add(std::forward(_fields)...); }; return rfl::apply(a, std::forward>(_tuple)); } /// Unfortunately, MSVC forces us to do this... template size_t calc_num_fields() const { const auto& extra_fields = get<_pos>(); if constexpr (std::is_pointer_v< std::remove_cvref_t>) { return size() + extra_fields->size() - 1; } else { return size() + extra_fields.size() - 1; } } /// Finds the position of the extra fields, or -1 if there aren't any. template constexpr static int find_extra_fields() { if constexpr (_i == size()) { return _idx; } else { using FieldType = internal::nth_element_t<_i, FieldTypes...>; constexpr bool is_extra_fields = internal::is_extra_fields_v; static_assert(_idx == -1 || !is_extra_fields, "There can only be one rfl::ExtraFields in any struct or " "named tuple."); if constexpr (is_extra_fields) { return find_extra_fields<_i + 1, _i>(); } else { return find_extra_fields<_i + 1, _idx>(); } } } /// Generates the fields. template auto make_fields(std::integer_sequence, AdditionalArgs&&... _args) && { const auto wrap = [this](Index<_i>) { using FieldType = internal::nth_element_t<_i, FieldTypes...>; return FieldType(std::move(rfl::get<_i>(values_))); }; return rfl::make_tuple(wrap(Index<_is>{})..., std::forward(_args)...); } /// Generates the fields. template auto make_fields(std::integer_sequence, AdditionalArgs... _args) const& { const auto wrap = [this](Index<_i>) { using FieldType = internal::nth_element_t<_i, FieldTypes...>; return FieldType(rfl::get<_i>(values_)); }; return rfl::make_tuple(wrap(Index<_is>{})..., _args...); } /// Generates a new named tuple with one value replaced with a new value. template auto make_replaced(V&& _values, T&& _val, std::integer_sequence) const { const auto wrap = [&](Index<_i>) { if constexpr (_i == _index) { return std::forward(_val); } else { using FieldType = internal::nth_element_t<_i, FieldTypes...>; using U = typename FieldType::Type; return FieldType(std::forward(rfl::get<_i>(_values))); } }; return NamedTuple(wrap(Index<_is>{})...); } /// Replaced the field signified by the field type. template NamedTuple replace_value(T&& _val) && { using FieldType = std::remove_cvref_t; constexpr auto index = internal::find_index(); return make_replaced(std::move(values_), std::forward(_val), seq_); } /// Replaced the field signified by the field type. template NamedTuple replace_value(T&& _val) const& { using FieldType = std::remove_cvref_t; constexpr auto index = internal::find_index(); auto values = values_; return make_replaced(std::move(values), std::forward(_val), seq_); } /// Adds the elements of a tuple to a newly created named tuple, /// and other elements to a newly created named tuple. template auto replace_tuple(rfl::Tuple&& _tuple) && { const auto r = [this](auto&&... _fields) { return std::move(*this).replace(std::forward(_fields)...); }; return rfl::apply(r, std::forward>(_tuple)); } /// Adds the elements of a tuple to a newly created named tuple, /// and other elements to a newly created named tuple. template auto replace_tuple(rfl::Tuple&& _tuple) const& { const auto r = [this](auto&&... _fields) { return this->replace(std::forward(_fields)...); }; return rfl::apply(r, std::forward>(_tuple)); } /// Retrieves the fields from another tuple. template constexpr static Fields retrieve_fields( rfl::Tuple&& _other_fields, std::integer_sequence) { const auto get_field = [&](Index<_i>) { constexpr auto field_name = internal::nth_element_t<_i, FieldTypes...>::name_; constexpr auto index = internal::find_index>(); using FieldType = internal::nth_element_t<_i, FieldTypes...>; using T = std::remove_cvref_t; return FieldType(std::forward(rfl::get(_other_fields).value_)); }; return rfl::make_tuple(get_field(Index<_is>{})...); } private: /// The values actually contained in the named tuple. /// As you can see, a NamedTuple is just a normal tuple under-the-hood, /// everything else is resolved at compile time. It should have no /// runtime overhead over a normal rfl::Tuple. Values values_; /// The position of rfl::ExtraFields, or -1 if there aren't any. constexpr static int pos_extra_fields_ = find_extra_fields(); }; // ---------------------------------------------------------------------------- /// We need a special template instantiation for empty named tuples. template <> class NamedTuple<> { public: using Fields = rfl::Tuple<>; using Names = Literal<>; using Values = rfl::Tuple<>; NamedTuple(){}; ~NamedTuple() = default; /// Returns a new named tuple with additional fields. template auto add(Field<_name, FType> _head, const Tail&... _tail) const { if constexpr (sizeof...(Tail) > 0) { return NamedTuple>(std::move(_head)).add(_tail...); } else { return NamedTuple>(std::move(_head)); } } /// Template specialization for rfl::Tuple, so we can pass fields from other /// named tuples. template auto add(rfl::Tuple _tuple, const Tail&... _tail) const { if constexpr (sizeof...(Tail) > 0) { return NamedTuple(std::move(_tuple)).add(_tail...); } else { return NamedTuple(std::move(_tuple)); } } /// Template specialization for NamedTuple, so we can pass fields from other /// named tuples. template auto add(NamedTuple _named_tuple, const Tail&... _tail) const { return add(_named_tuple.fields(), _tail...); } /// Returns an empty named tuple. template auto and_then(const F&) const { return NamedTuple<>(); } /// Does nothing at all. template void apply(F&&) const {} /// Returns an empty tuple. auto fields() const { return rfl::Tuple(); } /// Must always be 0. size_t num_fields() const { return 0; } /// Must always be -1. constexpr static int pos_extra_fields() { return -1; } /// Must always be 0. static constexpr size_t size() { return 0; } /// Returns an empty named tuple. template auto transform(const F&) const { return NamedTuple<>(); } /// Returns an empty tuple. auto values() const { return rfl::Tuple(); } }; // ---------------------------------------------------------------------------- template inline auto operator*(const rfl::Field<_name1, Type1>& _f1, const rfl::Field<_name2, Type2>& _f2) { return NamedTuple(_f1, _f2); } template inline auto operator*(const NamedTuple& _tup, const rfl::Field<_name, Type>& _f) { return _tup.add(_f); } template inline auto operator*(const rfl::Field<_name, Type>& _f, const NamedTuple& _tup) { return NamedTuple(_f).add(_tup); } template inline auto operator*(const NamedTuple& _tup1, const NamedTuple& _tup2) { return _tup1.add(_tup2); } template inline auto operator*(rfl::Field<_name1, Type1>&& _f1, rfl::Field<_name2, Type2>&& _f2) { return NamedTuple(std::forward>(_f1), std::forward>(_f2)); } template inline auto operator*(NamedTuple&& _tup, rfl::Field<_name, Type>&& _f) { return _tup.add(std::forward>(_f)); } template inline auto operator*(rfl::Field<_name, Type>&& _f, NamedTuple&& _tup) { return NamedTuple(std::forward>(_f)) .add(std::forward>(_tup)); } template inline auto operator*(NamedTuple&& _tup1, NamedTuple&& _tup2) { return _tup1.add(std::forward>(_tup2)); } } // namespace rfl #endif // RFL_NAMEDTUPLE_HPP_