$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-07-22 17:01:27
On Sunday 22 July 2001 03:17, you wrote:
> Douglas Gregor writes:
>  > >  If we take
>  > > the analogy of a "pointer to functor", you're fighting with the
>  > > difference between:
>  > >
>  > > 1) functor * p;
>  > > 2) functor const * p;
>  > > 3) functor * const p;
>  > > 4) functor const * const p;
>  > >
>  > > your current semantics seem to have (=> meaning "is analogous to"):
>  > >  functor => 1)
>  > >  const functor => 4)
>  > >
>  > > while the proposed semantics seem to be:
>  > >  functor => 1)
>  > >  const functor => 3)
>
> I made a few typos later on in what I wrote.  To clarify:
> 1) functor can be reseated, calls 'operator()' on its referent.
> 2) functor can be reseated, calls 'operator() const' on its referent.
> 3) functor cannot be reseated, calls 'operator()' on its referent.
> 4) functor cannot be reseated, calls 'operator() const' on its referent.
>
>  > I was proposing just:
>  > functor => 1
>  >
>  > and an auxiliary function object could (at 'function'
>  > assignment/initialization time) decide if for a particular target
>  > function object instance, it should be
>  > functor => 3
>
> I'm not quite sure what you mean by "auxiliary function object." Could
> you perhaps suggest some syntax?
See below...
> given the following:
>
> struct ftor {
>    void operator()();
>    void operator()() const;
> };
>
> void my_func(const function<void>& f) { f(); }
>
> what do you propose happens if I call my_func with a boost function
> pointing to 'ftor?  I'm under the impression that your proposal would
> call 'operator()()', which was what I meant above in my statement of
> your proposal.
Yes, the new semantics would call the non-const operator()() of ftor.
> I'm not sure I understand what you're saying here.  Could you write
> out a possible syntax for this?
So, if one wanted to call ftor::operator()() const, the 'auxiliary function 
object' would come into play. It would be a simple set of function objects 
like this:
template<typename F, typename R, typename T1, typename T2, ..., typename TN>
struct invoke_as_constN {
  invoke_as_constN(const F& f) : m_f(f) {}
  R operator()(T1 a1, T2 a2, ..., TN aN) const 
    { return m_f(a1, a2, ..., aN); }
  F m_f;
};
These classes would be used like this:
function<void> f;
function<void> fc;
f = ftor(); 
fc = invoke_as_const0<ftor, void>(ftor());
f(); // calls ftor::operator()()
fc(); // calls ftor::operator()() const via invoke_as_const0
>  > > One pertinent question is, is there a sensible use for a functor with
>  > > semantics 2) or 4).  That is, does it make sense to have a functor
>
> argh!  I meant 3) or 4) here, but I think you got the idea.  It makes
> sense to have a boost.functon that cannot be reseated regardless of the
> constness of the referent.
Yes.
>  > changed. However, see the reason in my prior post why having parallel
>  > function/const_function won't (and probably can't) work.
>
> quoted here:
>  > Perhaps. The one major difference between iterator/const_iterator and a
>  > function/const_function is that iterators have definitive "pointed-to"
>  > types, e.g., iterator to int and iterator to const int. The same does
>  > not quite apply to function, because it "points to" some
>  > dynamically-chosen type that always has an operator() const and may have
>  > an operator() as well. I think that because of this discrepancy the
>  > decision to call const or non-const shouldn't be up to the 'function'
>  > itself (i.e., function or const_function),
>
> Just some thought...suppose you had an iterator it over a container of
> functor base class objects with virtual 'operator()()'. If you call:
> (*it)();
> it would call the derived object's operator()().  if you have a
> "const iterator" (NOT const_iterator), the same thing would happen.
> The difference is that the iterator cannot be moved. This const
> iterator would be analogous to a "const boost.function" which could
> not be reseated.
>
> Now if you have a "const_iterator" cit into this container and call:
> (*cit)();
> it would call the derived's 'operator()() const'.  Of course an iterator
> of type "const const_iterator" would do the same, but not be movable.
> Supposing there were a boost.const_function, this would be the
> analogous type (if const, can't be reseated).
>
> now, on to your discussion below:
>  > The conversions may even be impossible :). Thinking about this, for
>  > instance, with the above my_function_object class:
>  >
>  > boost::function<int, int, int> f = my_function_object();
>  > boost::const_function<int, int, int> fc = f;
>  >
>  > So "f" should call my_function_object::operator() and fc should call
>  > my_function_object::operator() const? Can't happen: f doesn't even know
>  > my_function_object::operator() const exists, because it only references
>  > the non-const version, so what actually happens is that fc targets a
>  > copy of f, which targets the non-const my_function_object::operator()!
>
> I don't think that the above line should cause fc to target f as it
> would a function object of any other type.
> const_function would not target function, but cause a conversion from
> type function to type const_function. This is an altogether different
> beast.  Now, the big question is as follows:  Is it possible to detect
> during a conversion from function to const_function (either at compile
> or runtime) whether the referent of an already created boost.function
> has an 'operator() const'.
I believe the answer to this is 'no'. Once the assignment of a target to a 
boost::function object has been completed, the target's type information is 
lost. Anything we didn't lookup during the assignment we won't have access 
to. So if boost::function only looks up a non-const operator(), we don't know 
how to access operator() const when we copy the boost::function object into a 
boost::const_function object. Granted, we could lookup both operator() and 
operator() const, but then we're back to stub routines :(.
> take the following code:
> ---<snip>---
> struct my_functor {
>   void operator()() { } // no const
> };
>
> int main() {
> // ...
> my_functor* f = new my_functor();
> my_functor const* fc = f;
>
>  (*f)();
>  (*fc)();
>  return 0;
> }
> ---<snip>---
>
> A compiler should not accept it.  I like to think that the same sort
> of detection can be made in a conversion from boost.function to
> boost.const_function, but due to casting stuffs, perhaps it cannot be
> determined until runtime (I only vaguely understand the implementation
> of boost.function)? At which point a runtime fault of some sort
> sort could occur.
Having the ability to convert from boost.function to a boost.const_function 
requires a const version to always exist, even if it is invalid. Such a 
restriction can be a great annoyance. I think that using the wrapper 
(invoke_as_constN above, though perhaps will some sugar trickery to make it 
easier on the fingers) is the best alternative. Whether the const or 
non-const operator() is called should be dependent on the target ("pointee") 
of the boost.function, and not on whether a boost.function or a 
boost.const_function is used. 
>  > > As far as making the semantic change, I vote for doing so.  While
>  > > Boost.Function is already "out there", I think that the current
>  > > semantics confusing.  If/When these changes are released, A notice
>  > > should go in the Boost Release notes stressing such a semantic
>  > > change.
>  > >
>  > >
>  > > ron
>  >
>  > Does Boost have Release Notes?
>  >
>  > 	Doug
>
> I was thinking of what appears on the main web page after a release.
        Doug