Boost logo

Boost :

From: John Maddock (John_Maddock_at_[hidden])
Date: 2000-05-14 06:41:54


Mark,

I've had a chance now to have a look at your functional.hpp and on the
whole it works very well, and solves most of the existing problems with
<fuctional>, just a couple of comments though:

>2. What do you think of the packaging - should I split the single file
> up into separate files (e.g. negators.hpp, binders.hpp, etc)? (I'd
> prefer not to, but could be convinced people think it necessary.)

Keep it like it is as it's basically a replacement for <functional>, I
guess you could add call_traits optimised versions of std::plus etc as well
if you wanted to, but its not crucial.

>3. I use Borland C++Builder 4, but I've also tested it with BCC 5.5.
> How does it go on other platforms? For example, do you have any
> better luck with my attempt to make ptr_fun obsolete?

Yep it works very well with C++ Builder, but fails to compile with g++.
(As an asside its well worthwhile downloading a copy of the mingwin port of
gcc, used with -pedantic -ansi -Wall options it acts as a good sanity
check)

The problem is that g++ complains that your overloaded not1 and not2 are
ambiguous. Eventually I boiled this down to the following test:

#include <typeinfo>
#include <iostream>

int unary(int i){ return i; }

template <class Operation>
struct unary_traits
{
  static void trace()
  {
      std::cout << "Unspecialised unary_traits called...T is: " <<
typeid(unary_traits<Operation>).name() << std::endl;
  }
};

template <class R, class A>
struct unary_traits<R(*)(A)>
{
  static void trace()
  {
      std::cout << "Specialised unary_traits called...T is: " <<
typeid(unary_traits<R(*)(A)>).name() << std::endl;
  }
};

template <class T>
void trace_test(T t)
{
   std::cout << "type of argument is: " << typeid(t).name() << std::endl;
   std::cout << "type of T is: " << typeid(T).name() << std::endl;
   unary_traits<T>::trace();
}
template <class T>
void trace_test2(const T& t)
{
   std::cout << "type of argument is: " << typeid(t).name() << std::endl;
   std::cout << "type of T is: " << typeid(T).name() << std::endl;
   unary_traits<T>::trace();
}

int main()
{
   trace_test(unary);
   trace_test2(unary);
   return 0;
}

Compiled with bcc32 I see the following output:

type of argument is: int (*)(int)
type of T is: int (*)(int)
Specialised unary_traits called...T is: unary_traits<int,int>
type of argument is: int (*)(int) const
type of T is: int(int)
Unspecialised unary_traits called...T is: unary_traits<int(int)>

The problem here is trace_test2 which takes a functor by reference just
like your adapters do - the deduced parameter type is not a reference to a
function pointer, but a reference to a function, so that your traits class
gets instantiated not with a function pointer type, but with a function
type.

This explains two things: g++ can't decide between your overloaded
functions because using the name of a function alone is *both* a function
type and implicitly a function pointer type - in other words I don't think
your overloads are allowable C++ - it also explains why you needed those
overloads in the first place - your generic versions get passed a reference
to a function, not a pointer to a function.

This leaves us with three options:

1) go back to using ptr_fun.
2) pass functors by value to your functions, this solves the problems but
may cripple your codes efficiency in some cases.
3) find a better way.

Here's my attempt at something better for "not1", it uses an extra traits
class "make_functor" to coerce the adapter argument type into a function
pointer where appropriate, the implementation is based on using a extra
level of indirection (cf remove_cv from type_traits.hpp):

#include <typeinfo>
#include <iostream>
#include <functional>
#include <boost/call_traits.hpp>

namespace boost{

template <class Operation>
struct unary_traits
{
  typedef typename Operation::result_type result_type;
  typedef typename Operation::argument_type argument_type;
};

template <class R, class A>
struct unary_traits<R(*)(A)>
{
  typedef R result_type;
  typedef A argument_type;
};

template <class F>
struct make_functor_ind;

template <class T>
struct make_functor_ind<T*>
{
   typedef T type;
};

template <class R, class A>
struct make_functor_ind<R (*)(A)>
{
   typedef R(*type)(A);
};

template <class F>
struct make_functor : public make_functor_ind<F*>{};

template <class R, class A>
struct make_functor<R (*)(A)>
{
   typedef R(*type)(A);
};

//
--------------------------------------------------------------------------
// unary_negate, not1
//
--------------------------------------------------------------------------
template <class Predicate>
class unary_negate
  : public std::unary_function<typename
unary_traits<Predicate>::argument_type,bool>
{
public:
  explicit unary_negate(const Predicate &x)
      :
      pred(x)
  {}
  bool operator()(typename call_traits<typename
unary_traits<Predicate>::argument_type>::param_type x) const
  {
      return !pred(x);
  }
private:
  Predicate pred;
};

template <class Predicate>
unary_negate<typename make_functor<Predicate>::type> not1(const Predicate&
pred)
{
  return unary_negate<typename make_functor<Predicate>::type>((typename
make_functor<Predicate>::type)pred);
}

} // boost

bool f1(int i)
{ return (i == 0); }

int main()
{
   return boost::not1(f1)(0);
}

Note that there is only a single version of the not1 adapter - so g++ no
longer complains about ambiguous overloads, and both g++ and bcc32 cope
with the code now, without the need for ptr_fun. With luck you should be
able to adapt this method for your other adapters/binders and remove the
need for ptr_fun and make the code more portable.

Finally, there is a problem with probably no good solution: the current
adapters only work with extern "C++" function declarations, those declared
extern"C" should not be usable with these adapters (though most compilers
don't enforce this rule and treat extern "C" and extern "C++" as the same
type). There are also compiler specific extensions like __stdcall,
__fastcall etc, which mess things up, it would be nice if these adapters
(and the expression templates currently in the vault) could seemlessly work
with these function types. However we're into really compiler specific
behaviour here - I'm not even sure it's possible to write template
specialisations for these kinds of functions.

Anyway, enough, I hope these comments help!

- John.


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