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 x
s (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