c++ - A circular dependency involving comparison functors -
suppose need store information labeled e-mail messages. each message can assigned many labels. also, able retrieve messages assigned given label. here design:
class message; class label { public: ... private: std::string name_; std::set<std::shared_ptr<message>, std::function<bool(...)>> messages_; // message incomplete! }; class message { public: ... private: std::string title_; std::set<label *, std::function<bool(...)>> labels_; // fine }; each label stores set of messages label assigned. since set needs searchable message title, pass std::function comparison second template parameter of std::set. the problem: function object needs able access message's members. however, message incomplete type @ point.
the situation cannot fixed putting definition of message before definition of label, because have similar problem std::function passed set of labels (the line commented being fine in above code), needs searchable label name.
is there fix or better design this?
first, way map projection ordering:
template<class f> struct order_by_t { f f; using is_transparent = std::true_type; template<class lhs, class rhs> auto operator()(lhs&& lhs, rhs&& rhs)const -> decltype ( static_cast<bool>(f(std::declval<lhs>()) < f(std::declval<rhs>()) ) { return f(std::forward<lhs>(lhs)) < f(std::forward<rhs>(rhs)); } }; template<class f> order_by_t<std::decay_t<f>> order_by(f&& f) { return {std::forward<f>(f)}; } a projection takes type x , "projects" onto type y. trick here type y type of field want order our xs (in case, string, , projection takes x name of x).
this means have define projection (the mapping our type, part of type want order by), , feed order_by_t , generate ordering function us.
order_by_t seems stateful, doesn't have be. if f stateless, can order_by_t be! stateless means don't have initialize f, , can use it, , can lead compiler understanding code better (tracking state hard compilers, stateless things easy optimize).
or, in short, stateless better stateful. here stateless type wraps function call:
template<class sig, sig* f> struct invoke_func_t; template<class r, class...args, r(*f)(args...)> struct invoke_func_t<r(args...), f> { r operator()(args...args)const { return f(std::forward<args>(args)...); } }; example use:
void println( std::string const& s ) { std::cout << s << '\n'; } using printer = invoke_func_t< void(std::string const&), println >; and printer type instance of call println when use operator(). store pointer-to-println in type of printer, instead of storing copy of pointer inside of it. makes each instance of printer stateless.
next, stateless order_by wraps function call:
template<class sig, sig* f> struct order_by_f: order_by_t< invoke_func_t<sig, f> > {}; which 1 line, side effect of above being pretty polished.
now use it:
class message; class label; // impl elsewhere: std::string const& getmessagename( std::shared_ptr<message> const& ); std::string const& getlabelname( std::shared_ptr<label> const& ); class label { private: std::string name_; using message_name_order = order_by_f< std::string const&(std::shared_ptr<message> const&), getmessagename >; std::set<std::shared_ptr<message>, message_name_order > messages_; }; where jumped through bunch of hoops make clear std::set ordering calling getmessagename , calling < on returned std::string const&s, 0 overhead.
this can done simpler more directly, each of onion layers wrote above (especially order_by).
the shorter version:
class message; bool order_message_by_name( std::shared_ptr<message> const&, std::shared_ptr<message> const& ); class label { private: std::string name_; std::set<std::shared_ptr<message>, bool(*)(std::shared_ptr<message>const&, std::shared_ptr<message>const&) > messages_; // message incomplete! label(std::string name):name_(std::move(name)), messages_(&order_messages_by_name) {} }; where store function pointer in our set tells class how order it.
this has run time costs (the compiler have difficulty proving function pointer points same function, have store , dereference on each ordering call), forces write order_messages_by_name (an ugly specific-purpose function), , has maintenance costs (you have prove function pointer never changes whenever think set).
plus, doesn't give cool order_by function, you'll love every time want sort std::vector except <.
Comments
Post a Comment