
On 4/29/07, Peter Dimov <pdimov@mmltd.net> wrote:
Daniel Walker wrote:
!boost::bind(...): Bind
Task for bind: None. This works as is.
Task for lambda: operator! needs to be supplied for std::bind but disabled for boost::bind.
I'm planning to propose std::bind relational operators and operator! for the next meeting. They may or may not actually go in, of course. I'm still not sure that I have the design 100% right, though. The basic idea is to add
template<class L, class R> typename enable_if< is_bind_expression<L>::value || is_placeholder<L>::value || is_bind_expression<R>::value || is_placeholder<R>::value, ...>::type operator==( L const & l, R const & r ) { return bind( __equal_to(), l, r ); }
where __equal_to()(x,y) returns x == y.
The interesting issue is in which namespace we need to add the above. The global namespace works most of the time, is contrary to the spirit of the rest of the standard, and fails when an operator== overload in the current scope hides the global one.
How about this. Dumping operator== into the global namespace is bad. Dumping it into a namspece with a type that you're making EqualityComparible (thus enabling ADL) is normal. But if operator== no longer means EqualityComparible and instead means delayed evaluation, then I think that's not a good idea. I like something similar to lambda's approach of putting the operators in a separate namespace. The user enables delayed evaluation of operators by importing them via a using directive. So, let's consider a namespace separated from types that may or may not be EqualityComparible (i.e. separated from both std and std::placeholders). The sole purpose of this namespace is to provide an interface for users to enable bind expressions from operators. So, let's call it std::bind_expressions, or if it's not too terse, just std::expressions. So the user could do something like. using std::bind; // enable delayed functions using std::placeholders; // enable currying using std::expressions; // enable delayed operator expressions Now, consider a user who wants to make functors equality comparable, perhaps because some are multithreaded and need to be treated differently than others. The user wants to be able to compare user defined functors and std functors (even functors resulting from std::bind) transparently. So, the user defines generic operator== overloads in the namespace user::comparison. Now, the user can do ... user::user_functor f; { using namespace user::comparison; f == f; // user comparison is true f == std::tr1::bind(f); // user comparison is false } If the user instead wanted to delay the comparison using bind (with namespace expressions in tr1 for notational consistence) the code above would change to ... { using namespace user::comparison; f == f; // user comparison is true } { using namespace std::tr1::expressions; f == std::tr1::bind(f); // delay == } If the user wanted to be able to delay and compare, any delayed expression operator can be imported individually ... { using namespace user::comparison; f == f; // user comparison is true { using std::tr1::expressions::operator==; f == std::tr1::bind(f); // delay == } f == std::tr1::bind(f); // user comparison is false } This would work similarly for placeholders. Now, if the user had defined the comparison operators in namespace user instead of namespace user::comparison then the statements where delayed expressions were in scope would be ambiguous due to ADL. However, this gets back to the more general problem of combining types with the same overloaded operators in the same expressions. You can't override ADL by qualifying the scope of an operator like 'x std::tr1::expression::== y'. You could say 'std::tr1::expression::operator==(x, y)', but that gets cumbersome. A general mechanism for telling an operator to claim an argument like std::tr1::expression::hold(x) == y could do the trick, but this is also kind of cumbersome. I'm not familiar with Herb Sutter's ADL proposal, so I don't know if this would help matters. Still, I think putting the operators in a separate namespace is a fair trade. Users can define bind compatible relops or ADL compatible relops for their types but not both without requiring cumbersome syntax like std::tr1::expression::operator==(x, y); i.e if they want the easy syntax they have to chose one or the other. I think this is reasonable because bind relop likely has completely different semantics than any user defined relop found via ADL.
This is also an interesting use case for a || concept requirement. I'm not terribly familiar with the concepts proposal though; it might offer an alternative solution that I don't know about.
What do you mean by 'a || concept requirement'? I'm not sure that any concept would help in my example above. What might help is namespace qualification for scope operators. Below, there's a complete example using the code snippets if you want to copy, past and tweak. It needs a tr1 compliant standard library. I'm not sure, but I think Boost.TR1 won't work because the operators are already defined in namespace boost. I compiled with ... g++ -I/usr/include/c++/4.1/tr1 file.cpp Daniel #include <functional> #include <boost/mpl/int.hpp> #include <boost/mpl/logical.hpp> #include <boost/type_traits.hpp> #include <boost/utility/enable_if.hpp> namespace std { namespace tr1 { template<class F, class A0, class A1> class bind_expression { static F f; static A0 a0; static A1 a1; public: typedef typeof(bind(f, a0, a1)) type; }; namespace expressions { template<class L, class R> typename boost::enable_if< boost::mpl::or_< boost::mpl::int_<is_bind_expression<L>::value> , boost::mpl::int_<is_bind_expression<R>::value> > , typename bind_expression< equal_to<L>, L, R >::type
::type operator==( L const & l, R const & r ) { return bind( equal_to<L>(), l, r ); }
}}} // end std namespaces namespace user { class user_functor { int data; public: typedef int result_type; int operator()() { return data; } bool operator==(user_functor const& that) { return this->data == that.data; } }; template<class Functor> struct is_user_functor { static const bool value = false; }; template<> struct is_user_functor<user_functor> { static const bool value = true; }; namespace comparison { template<class L, class R> typename boost::enable_if< boost::mpl::and_< boost::mpl::int_<is_user_functor<L>::value> , boost::is_same<L, R> > , bool
::type operator==(L l, R r) { return l.operator==(r); }
template<class L, class R> typename boost::disable_if< boost::mpl::and_< boost::mpl::int_<is_user_functor<L>::value> , boost::is_same<L, R> > , bool
::type operator==(L l, R r) { return false; }
}} // end user namespaces int main() { user::user_functor f; { using namespace user::comparison; f == f; // user comparison is true f == std::tr1::bind(f); // user comparison is false } { using namespace user::comparison; f == f; // user comparison is true } { using namespace std::tr1::expressions; f == std::tr1::bind(f); // delay == } { using namespace user::comparison; f == f; // user comparison is true { using std::tr1::expressions::operator==; f == std::tr1::bind(f); // delay == } f == std::tr1::bind(f); // user comparison is false } }