Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-07-20 14:41:42

The question of Boost.Function's operator() and/or operator() const has
reared its ugly head again. It was originally discussed in this thread:

Another dimension (using binders with Boost.Function) goes against the
conclusion drawn in that thread. I'll recap the problem, solution, and its
problems here:

Boost.Function is a set of function object wrappers for deferred calls.
Should the function call operator be const, non-const, or should both be

Currently, the implementation supplies both const and non-const version. The
function object target is then invoked as const if the const version was
used, and non-const if the non-const version was used. Benefits of this
approach are:
        - Boost.Function objects can be dropped in without any semantic changes (see
code snippet A, below).
        - Allows target function objects to have both const and non-const function
call operators accessible through Boost.Function objects.

Disadvantages are:
        - const and non-const function call operators have the same return type, so
one of the most common uses of giving both a const and a non-const operator()
isn't possible anyway (i.e., return a reference-to-T for operator() and a
reference-to-const T for operator() const).
        - target function objects _must_ have operator() const defined, even if it
doesn't make sense. One user has been burned by this already (the stub const
version was being called from a const Boost.Function object, but the stub
routine didn't throw an exception or assert to make the problem obvious).
        - &boost::function<...>::operator() requires an extra casetto choose const
or non-const operator() (makes it tough to use binders with Boost.Function,
see code snippet B).

The final point in the "disadvantages" swayed me. I believe it would be best
to remove operator() completely (thereby leaving only operator() const)
        1) Boost.Function can be considered to be a handle or pointer to a function
object, so constness of Boost.Function is disjoint from constness of the
targetted function object.
        2) It doesn't force users into creating stub routines, so users can always
pass Boost.Function objects around as reference-to-const without worrying
about triggering a stub.
        3) Calls to operator() don't modify the Boost.Function object, so it should
be const.
        4) Easier to use binders, see code snippet B.

I'm very leary of making such a semantic change now that Boost.Function is
available, but I do believe that the resulting interface is cleaner and more
consistent. Code snippet A describes the resulting semantic inconsistency
between using a function object directly and using the function object
wrapped in a Boost.Function. Note that both the const and the non-const "do"
_must_ have the same type if "F" is a Boost.Function, so the usefulness of
having const and non-const operator() overloads is diminished already.

// Code snippet A
template<typename F>
struct some_type {
  some_type(F fin) : f(fin) {}

  int do(int x) { return f(x); }
  int do(int x) const { return f(x); }

  F f;

struct my_function {
  int operator(int x) { return x; }
  int operator(int x) const { return -x; }

some_type<my_function> st1(my_function());
some_type<boost::function<int, int> > st2(my_function());

With the current semantics of Boost.Function, st1 and st2 will have the same
behavior. With the proposed semantic change, a call to st2::do() const will
still call the non-const my_function::operator().

// Code snippet B
typedef boost::function<void, int> func;
std::vector<func> funcs;

std::for_each(funcs.begin(), funcs.end(),
  std::bind2nd(std::mem_fun_ref(&func::operator()), 0));

Any comments would be appreciated. It seems that the impact of this change on
users will be very minimal, and the benefits outweight any changes, but I
would prefer some help in this decision.


Boost list run by bdawes at, gregod at, cpdaniel at, john at