Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSDK-6636 - refactor AttributeMap and ProtoType #221

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

stuqdog
Copy link
Member

@stuqdog stuqdog commented Mar 13, 2024

Currently this is just changes to proto_type.hpp, still a WIP

Assuming we're happy with the shape of this, I will go about renaming the file and updating the rest of the code base.

@stuqdog stuqdog requested a review from acmorrow March 13, 2024 14:40
@stuqdog stuqdog requested a review from a team as a code owner March 13, 2024 14:40
@stuqdog stuqdog requested review from njooma and purplenicole730 and removed request for a team March 13, 2024 14:40
@stuqdog stuqdog marked this pull request as draft March 13, 2024 17:43
@stuqdog stuqdog marked this pull request as ready for review March 18, 2024 19:20
}
class value_type;

using map_type = std::unordered_map<std::string, value_type>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, my understanding is that unordered_map with an incomplete type has undefined behavior. It may happen to work depending on the implementation. For C++17, some standard container types were given defined behavior when used with incomplete types, but I think it is limited to std::vector, std::list, and std::forward_list:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, will change to a unique_ptr

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to change to unique ptr. You just can't declare this typedef with value_type as an incomplete type. But the recursive_wrapper stuff can handle it. Adding unique_ptr is just doubling up on the indirection that recursive_wrapper already provides.

using base_value_types_ =
boost::variant<boost::blank, std::string, const char*, int, float, double, bool>;
using value_types = boost::make_recursive_variant<
base_value_types_,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look quite right to me. It looks like this is declaring a variant within a variant. Did you mean to use mpl and make_recursive_variant_over? Or does make_recursive_variant treat the first type specially in way I don't know about?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I agree that seeing boost::variant and then boost::make_recursive_variant on it immediately after is a little strange. My thinking though was that we still wanted the root type to be a variant. So in essence the three cases for a value type are

| boost::variant<...>,
| std::vector<boost::variant<...>>,
| std::unordered_map<std::string, boost::variant<...>>,

In playing around with this definition I was able to build a value_type with raw values, with lists, with maps, with nested lists/maps, etc. etc. I also got the sense that make_recursive_variant_over was largely equivalent to make_recursive_variant, per description here.* That said, I can definitely believe I'm missing something and that use of mpl/make_recursive_variant_over would produce superior results!

  • type has behavior equivalent in every respect to [make_recursive_variant](https://www.boost.org/doc/libs/1_84_0/doc/html/boost/make_recursive_variant.html)< Sequence[0], Sequence[1], ... >::type (where Sequence[i] denotes the i-th element of Sequence), except that no upper limit is imposed on the number of types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, make_recursive_variant_over is equivalent. What I'm puzzled about is why there are two levels of variant, rather than just:

boost::make_recursive_variant<boost::blank, ..., bool, std::unordered_map<std::string, boost::recursive_variant_>, std::vector<boost::recursive_variant_>>

Why the second level of variant? I wouldn't expect it to be needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see, that makes sense. Yeah I don't think the second level of variant is needed, will fix.

};

} // namespace prototype_details
} // namespace value_type_details
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of get_helper was to specialize for the AttributeMap case to deal with the extra layer of pointer with an automatic dereference in the populated case. Since the specialization has been removed, it suggests that get_helper is no longer needed. But I have a suspicion it still may need to exist, but with different specializations.

Comment on lines 64 to 66
value_type(std::vector<value_type> v);
value_type(map_type);
value_type(google::protobuf::Value value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For at least these types, copying is pretty expensive. I recommend providing both a const& and a && overload.

Alternatively, you could write a universally forwarding constructor:

template<typename T>
value_type(T&& t)

However, that tends to be too greedy without some SFINAE to make sure it doesn't get invoked when it shouldn't.

value_type(double d);
value_type(bool b);
value_type(std::vector<value_type> v);
value_type(map_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constructor is going to prove to be troublesome due to the containers vs incomplete types issue from above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yeah, wrapping these in unique_ptr now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't help though, because the container type still can't handle the forward declaration reliably.

Copy link
Member Author

@stuqdog stuqdog Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I was misunderstanding. Thanks for clarifying! If I put the using map_type = ... inside value_type class proper, is that allowed? I assume so but am tripping myself up a bit because at whatever point the using line exists, the class isn't fully defined at that point.

public:
template <typename T>
auto get() {
return value_type_details::get_helper<T>::get(value_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without any specialization for get_helper<T>, these just decay into boost::get. But I think you may want new specializations? If a user asks for the std::vector<value_type> member, does it get you it, or do you get the wrapper? I'm not actually sure.

Comment on lines 81 to 83
// CR erodkin: consider how to return a ref or pointer to the value type such that we don't have
// to copy the `value_type`, while retaining guarantees of const-ness.
value_type get(const std::string& key) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a problem with just having:

const value_type& get(...) const;
value_type& get(...);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I recall getting compiler warnings when I tried that before but it seems to be fine now so I must be misremembering! Yeah this looks good.


// CR erodkin: consider how to return a ref or pointer to the value type such that we don't have
// to copy the `value_type`, while retaining guarantees of const-ness.
value_type get(const std::string& key) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What value_type gets returned if the item is not found? Or does it throw?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good question. My intuition is returning a boost::blank as the default is best, people can check for the "not found" case pretty easily (if (!foo)) and it saves having to potentially large chunks of code in a try/catch.

Comment on lines 85 to 95
// set a new k/v pair in the AttributeMap
void set(std::string key, value_type::value_types value) const;
void set(std::string key, value_type value) const;

// append a single value to a list type within the AttributeMap
void append(const std::string& key, value_type::value_types value);
void append(const std::string& key, value_type value);

// add a single key/value pair to a map type within the AttributeMap
void insert(const std::string& key, std::pair<std::string, value_type>);
void insert(const std::string& key, std::pair<std::string, value_type::value_types>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may just be better to expose the map_ rather than try to define your own wrapper above the unordered_map interface.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree this is trying to do to much. I'm wondering though if it's better to keep the AttributeMap class around and expose map_ or to just define value_type at the top level and have AttributeMap just be a typedef on the map type. The latter seems more intuitive to me personally, though it does perhaps make get helpers slightly more awkward if they're just free standing functions rather than a class method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants