Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-12-28 08:56:11


On Thursday 27 December 2001 07:07 pm, you wrote:
> Because I still do not believe that under the best of circumstances that
> compilers will improve that much. I have been coding SigC++ for over
> 2 years and still have the same petty problems with most of the
> compilers out there. This compiler doesn't like void returns, that
> one doesn't like traits for references, this one does but doesn't use
> them unless it is the first Tueday of the third week in May.

I understand the problems with portability, and it consumes more than half of
my library design time. It's obnoxious, but to compromise the quality of an
interface because of it? What will give compiler implementors a reason to
implement the entire C++ language if we're all using only the features of C
with Classes? Look for the ideal interface, then mangle the implementation to
make it work. The only good excuse for a non-ideal interface is when the
language does not allow it.

> > To return a function<int, int, int>, one needs to know the exact argument
> > and return types of foo; this isn't so easy if foo is a function object
> > with multiple operator()'s.
>
> Then have the used do....
> template <class T>
> struct my_functor
> {
> typedef void return_type;
> typedef args<int,int> args_type;
> void operator()(int, int) {};
> }

Can't really do that, because function<int, int, int> should be able to deal
with all types of function objects, and args_type is not part of the function
object concept in the majority of libraries.

> > The difference, to me, is that returning a concrete
> > type means two things: we're erasing type information (since the actual
> > object that is called has a type that can't b exported through the
> > function<int, int, int>) and we're going to pay a price in efficiency if
> > we don't need the type erasure. bind(foo, _1, 1) passed directly to
> > std::transform is more efficient if it does not return a concrete type;
> > more flexible, too, with regard to multiple or templated operator()'s.
>
> Multiple typed operator() with a callback is just plan evil. You
> will never get a compiler to generate any sort of meaningful error
> to the user with such a construct.

I wouldn't call them evil, and they are very common in binding libraries. For
instance, a valid expression when using the Lambda library would be:
  _1 + 3*_2

this is a binary function object that adds its first argument to 3 times its
second argument. No types are specified, because they are deduced when the
function object is used, e.g.,
  (_1 + 3*_2)(3, 5) = 18 (an int)
  (_1 + 3*_2)(1.5, 2) = 7.5 (a double)

It's quite cumbersome and not useful at all to specify types for every
argument.

> > Concrete typing has to exist at some point, of course. But to say that
> > the overhead will be higher just because the concrete type itself is a
> > template parameter is misguided. What's the real difference between
> > std::list<my_callback_type> and std::list<your_callback_type> if
> > my_callback_type and your_callback_type are roughly equivalent in
> > efficiency and size?
>
> If every one of your types potentially has a different adaptor sets
> and those types must be exposed then there is a huge difference, mine
> compiles!

Again, we obviously need to have a concrete type at some point. Otherwise
std::list<callback_type> isn't going to work at all. But there are many, many
callback classes floating around; some are very general, but carry with them
perhaps a larger efficiency burden, whereas some are very specific and very
fast. Because of this, the user needs to be able to choose what sort of
callback they want to deal with. This also puts the strict vs. non-strict
argument typing debate squarely in the user's hands.

> You continue to gloss the issue that a concrete type library is needed
> in that you can not declare a
>
> template <class T> virtual void connect(const T& t);

I agree with you that a concrete type is required to pass slots through
virtuals. I don't, however, see the need for such a type to be passed around
the adaptors. One could use something such as:

template<typename Callback>
class slot {
public:
  template<typename F> slot(const F& f) : callback(f)
  {
    // tracking code
  }

private:
  Callback callback;
  // tracking variables
};

Then slot<callback_type> is a holder for a slot that can be safely passed
around. Then this is fine:

  virtual void connect(const slot<callback_type>&);

The implicit conversion to slot<callback_type> allows the syntax I like (with
adaptors decoupled) but also provides a concrete type to use.

        Doug


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