Python 'in' keyword-like in C++

Any boost library can make me write the expression like int x = 3; if ( x in list(3, 5, 6, 7) ) { ... } -- Tzu-Chien Chiu, 3D Graphics Hardware Architect <URL:http://www.csie.nctu.edu.tw/~jwchiu>

"Tzu-Chien Chiu" wrote:
Any boost library can make me write the expression like
int x = 3; if ( x in list(3, 5, 6, 7) ) { ... }
The library suggested here http://lists.boost.org/Archives/boost/2005/06/88630.php has such feature. I do not know whether Sandbox has the latest version, if not, ask Maksym directly. /Pavel

Tzu-Chien Chiu wrote:
Any boost library can make me write the expression like
int x = 3; if ( x in list(3, 5, 6, 7) ) { ... }
I don't know about the boost libraries, but I quickly sketched out something you might be interested in, based on something I saw on one of the news groups. All you have to do is to write a binary predicate functor with bool operator()(int,<whatever your list type is>). It's untested, but a quick syntax check with g++ 3.4.2 works fine. Usage will be if (x <in> list(3,5,6,7) ) { ... } I hope that's more or less what you're after; I don't know if you want that list type/function as well. The code itself is attached. Be sure to fix the template parameter in the unnamed namespace.. David Hall template <class F> struct operator_t { operator_t(F f=F()): f(f) {} F f; }; // Make a binary predicate functor that takes two arguments that iterates over your list... namespace { operator_t<YOU FILL IN HERE> in; } namespace detail { template <class T, class F> struct operator_t_bind { operator_t_bind(const T & x,F & f):lhs(x),f(f) {} const T & lhs; const F & f; }; } template <class T,class F> detail::operator_t_bind<T,F> operator<(const T & lhs,operator_t<F> & op) { return detail::operator_t_bind<T,F>(lhs,op.f); } template <class T, class F,class RHS> bool operator>(const detail::operator_t_bind<T,F> & binder,const RHS & rhs) { return binder.f(binder.lhs,rhs); }

David Hall wrote: > Tzu-Chien Chiu wrote: > >> Any boost library can make me write the expression like >> >> int x = 3; >> if ( x in list(3, 5, 6, 7) ) { >> ... >> } >> >> >> > I don't know about the boost libraries, but I quickly sketched out > something you might be interested in, based on something I saw on one of > the news groups. All you have to do is to write a binary predicate > functor with bool operator()(int,<whatever your list type is>). It's > untested, but a quick syntax check with g++ 3.4.2 works fine. > > Usage will be > if (x <in> list(3,5,6,7) ) { ... } > > I hope that's more or less what you're after; I don't know if you want > that list type/function as well. > > The code itself is attached. Be sure to fix the template parameter in > the unnamed namespace.. > > David Hall > > > ------------------------------------------------------------------------ > > _______________________________________________ > Boost-users mailing list > Boost-users@lists.boost.org > http://lists.boost.org/mailman/listinfo.cgi/boost-users Huh.. I never thought to overload them that way, I like it! Some small issues with the implementation: * in operator_t<F>: your ctor init is... dodgy at best. Rename the parameter _f or something (ie: operator_t(F _f = F()) : f(_f) {}) * Cleaner layout! indent evenly, return types and init lists on their own lines, not seperating & from types, spaces after comma's in parameter lists, etc... Makes comprehension easier, for you _and_ your readers. * related note: more meaningful template parameter names: BinFn instead of F, ValueT instead of T, etc... * It should be designed as an expression template library, 'in' is something that operator< can recognise as an infix op, and generate some binder functional that when it sees an operator> can call the op on the values to its left (stored) and right. I'd have to take a look at what some of the expression template functions do to come up with an implementation. This way <in> works for everything, and, if you are careful, you could generalise to pretty much operator that you want, and the same operator< template function can create the right thing. I'll play around with it over the weekend and see what I can come up with. Boost.Infix?

Played around a bit, try this. It only defines is_in (which wraps 'std::find(rhs.begin(), rhs.end(), lhs) != rhs.end()' ), but should be easily generalisable to any operation, say: if(x <is_in> vec) {while(int i <for_all_in> vec) cout << i;} (I don't know if you could do the last one, or what syntax would be better suited, but you get the idea) #include <vector> #include <algorithm> namespace boost { namespace infix { namespace detail { template <typename LhsT, typename Operator> struct binder { explicit binder(LhsT& _lhs) : lhs(_lhs) {} template <typename RhsT> typename Operator::template sig<LhsT, RhsT>::type operator > (const RhsT& rhs) { return Operator::apply(lhs, rhs); } // SFINAE version of the above using ::result_type // ... private: LhsT& lhs; }; // lots of other things } struct is_in_operator { template <typename ValueT, typename Cont> static bool apply(const ValueT& val, const Cont& cont) { return std::find ( cont.begin() , cont.end() , val ) != cont.end(); } template <typename LhsT, typename RhsT> struct sig { typedef bool type; }; } is_in; // Lots of other infix operators template <typename LhsT, typename Operator> detail::binder<LhsT, Operator> operator < (LhsT& lhs, Operator rhs) { return detail::binder<LhsT, Operator>(lhs); } }} int main() { using namespace boost::infix; std::vector<int> vec; int val = 6; vec.push_back(4); vec.push_back(7); vec.push_back(6); return val <is_in> vec; }

Simon Buchan wrote:
Played around a bit, try this. It only defines is_in (which wraps 'std::find(rhs.begin(), rhs.end(), lhs) != rhs.end()' ), but should be easily generalisable to any operation, say: if(x <is_in> vec) {while(int i <for_all_in> vec) cout << i;} (I don't know if you could do the last one, or what syntax would be better suited, but you get the idea)
Anything like "while(int i <for_all_in> vec) cout << i;" isn't all that feasible (without being really hackish), since you need to maintain some sort of state inside of something that lives beyond each while statement, but specifically not in for_all_in, since we want to be able to iterate over the same container twice. The best I can come up with is this: (This could be prettified if we had auto, with the added bonus of allowing for autoselection of const versus non-const iterators.) for_all_in_iterator<std::vector<int> > i; while( i <for_all_in> vec) std::cout << *i << std::endl; where for_all_in_iterator is essentially a specialized form of boost::optional<>. I don't think you can get around using an iterator, unless you're willing to sacrifice either for_all_in's reentry or status as pseudo-keyword. Either way, I added my code in, with a few edits to yours, to allow for non-const RhsT. Ideally, there'd be a for_all_in_const_iterator version, but that's pretty easy. David Hall #include <vector> #include <algorithm> #include <iostream> #include <cassert> namespace boost { namespace infix { namespace detail { template <typename LhsT, typename Operator> struct binder { explicit binder(LhsT& _lhs) : lhs(_lhs) {} template <typename RhsT> typename Operator::template sig<LhsT, RhsT>::type operator > (RhsT& rhs) { return Operator::apply(lhs, rhs); } template <typename RhsT> typename Operator::template sig<LhsT, RhsT>::type operator > (const RhsT& rhs) { return Operator::apply(lhs, rhs); } // SFINAE version of the above using ::result_type // ... private: LhsT& lhs; }; // lots of other things } struct is_in_operator { template <typename ValueT, typename Cont> static bool apply(const ValueT& val, const Cont& cont) { return std::find ( cont.begin() , cont.end() , val ) != cont.end(); } template <typename LhsT, typename RhsT> struct sig { typedef bool type; }; } is_in; // Ugly version of the adapter pattern, perhaps too similar to boost::optional // A state of valid implies that for_all_in has been applied to this iterator at least once. template <class Cont> class for_all_in_iterator { typedef typename Cont::iterator real_iter_t; typedef typename Cont::value_type value_type; public: for_all_in_iterator():valid(false) {} for_all_in_iterator(const for_all_in_iterator & rhs) : valid(rhs.valid), iter(rhs.iter) {} for_all_in_iterator(const real_iter_t & rhs) : valid(false), iter(rhs) {} for_all_in_iterator & operator=(const real_iter_t & rhs) { iter = rhs; return *this; } real_iter_t & operator->() { return iter; } const real_iter_t & operator->() const { return iter; } value_type & operator*() { return *iter; } const value_type & operator*() const { return *iter; } for_all_in_iterator & operator++() { ++iter; return *this; } for_all_in_iterator operator++(int) { for_all_in_iterator tmp(*this); operator++(); return tmp; } bool operator==(const for_all_in_iterator & rhs) const { return iter == rhs.iter; } bool operator!=(const for_all_in_iterator & rhs) const { return iter != rhs.iter; } bool operator<(const for_all_in_iterator & rhs) const { return iter < rhs.iter; } bool operator>(const for_all_in_iterator & rhs) const { return iter > rhs.iter; } bool operator<=(const for_all_in_iterator & rhs) const { return iter <= rhs.iter; } bool operator>=(const for_all_in_iterator & rhs) const { return iter >= rhs.iter; } // Let's not be picky here. template <class OtherIter> bool operator==(const OtherIter & rhs) const { return iter == rhs; } template <class OtherIter> bool operator!=(const OtherIter & rhs) const { return iter != rhs; } template <class OtherIter> bool operator<(const OtherIter & rhs) const { return iter < rhs; } template <class OtherIter> bool operator>(const OtherIter & rhs) const { return iter > rhs; } template <class OtherIter> bool operator<=(const OtherIter & rhs) const { return iter <= rhs; } template <class OtherIter> bool operator>=(const OtherIter & rhs) const { return iter >= rhs; } private: bool valid; real_iter_t iter; friend class for_all_in_operator; }; struct for_all_in_operator { template <typename Cont> static bool apply (for_all_in_iterator<Cont> & iter, Cont& cont) { if(!iter.valid) { iter.iter = cont.begin(); iter.valid = true; return true; } if(iter.iter == cont.end()) return false; return ++iter != cont.end(); } template <typename LhsT, typename RhsT> struct sig { typedef bool type; }; } for_all_in; // Lots of other infix operators template <typename LhsT, typename Operator> detail::binder<LhsT, Operator> operator < (LhsT& lhs, Operator rhs) { return detail::binder<LhsT, Operator>(lhs); } }} int main() { using namespace boost::infix; std::vector<int> vec; int val = 6; vec.push_back(4); vec.push_back(7); vec.push_back(6); assert(val <is_in> vec); // The following could be a lot prettier with auto, oh well. for_all_in_iterator<std::vector<int> > i; while( i <for_all_in> vec) std::cout << *i << std::endl; }

David Hall wrote:
Anything like "while(int i <for_all_in> vec) cout << i;" isn't all that feasible (without being really hackish), since you need to maintain some sort of state inside of something that lives beyond each while statement, but specifically not in for_all_in, since we want to be able to iterate over the same container twice.
Well, I didn't really mean for that to be taken _too_ seriously, I just was throwing out general concepts.
The best I can come up with is this: (This could be prettified if we had auto, with the added bonus of allowing for autoselection of const versus non-const iterators.) for_all_in_iterator<std::vector<int> > i; while( i <for_all_in> vec) std::cout << *i << std::endl;
where for_all_in_iterator is essentially a specialized form of boost::optional<>. I don't think you can get around using an iterator, unless you're willing to sacrifice either for_all_in's reentry or status as pseudo-keyword.
Gosh! You didn't need to do all that!
Either way, I added my code in, with a few edits to yours, to allow for non-const RhsT. Ideally, there'd be a for_all_in_const_iterator version, but that's pretty easy.
Yes, you need the overloads for rvalue arguments, to do things like 'if(6 <is_in> foo) ...' I put that in after reading up on 'perfect forwarding' (although I used the equivilant '(T const & foo)' and did it for LhsT _and_ RhsT, just to 1-up you :D)

David Hall wrote: <snip>
Huh.. I never thought to overload them that way, I like it! Some small issues with the implementation: * in operator_t<F>: your ctor init is... dodgy at best. Rename the parameter _f or something (ie: operator_t(F _f = F()) : f(_f) {})
My bad, it was a coding guideline at some point for some class.
We've all done it ;)
* Cleaner layout! indent evenly, return types and init lists on their own lines, not seperating & from types, spaces after comma's in parameter lists, etc... Makes comprehension easier, for you _and_ your readers.
* related note: more meaningful template parameter names: BinFn instead of F, ValueT instead of T, etc...
Again, my bad. I sketched it out in about 5 minutes...
Heck, just a note, I'm not the layout police :D
* It should be designed as an expression template library, 'in' is something that operator< can recognise as an infix op, and generate some binder functional that when it sees an operator> can call the op on the values to its left (stored) and right. I'd have to take a look at what some of the expression template functions do to come up with an implementation. This way <in> works for everything, and, if you are careful, you could generalise to pretty much operator that you want, and the same operator< template function can create the right thing. I'll play around with it over the weekend and see what I can come up with. Boost.Infix?
I like your version a lot better, and I like the name. :) Let me know if you want me to help with anything; if you do, I promise to be neater. That said, I doubt you need it/want it.
No no, jump right in! I'm not sure I could spend enough time on a library like this to give it all the luv'n it would need!
Also, I think you can make unary operators work too, though you'd probably need to use $my_unary_op$ (expression) instead, where $ is one of *, -, +, and &. The only problem is that they would have lower precedence than -> and ., meaning that the semantics of "op ptr->field" would change if you used a builtin unary operator versus one of these. Another option would be to use either () or [], but then I suppose you might as well be writing a function.
Well, op expr -> op(expr) is not _nearly_ as bad as expr op expr -> op(expr, expr), so I dont have too much trouble with function call. If we can get nice clean syntax worked out for a cohesive library, that would be cool, though.
If you still think it's worth it, I'd lean towards * or +, but that's just me. Perhaps one might even change the entire naming scheme to one of those so that unary and binary operators are consistent. Using & might run into (worse) problems because of its binary precedance, and I think - is ugly. Shall I write up a (clean) version of unary named operators, based on your code?
If you want, I don't own you, or the idea (it was your idea for the concept, remember? :D)
participants (4)
-
David Hall
-
Pavel Vozenilek
-
Simon Buchan
-
Tzu-Chien Chiu