Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2007-04-30 14:08:44


On 4/29/07, Peter Dimov <pdimov_at_[hidden]> 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
    }
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk