roxrook
roxrook

Reputation: 13853

How to make negate_unary work with any type?

Following this question: How to negate a predicate function using operator ! in C++?
I want to create an operator ! can work with any functor that inherited from unary_function. I tried:

template<typename T>
inline std::unary_negate<T> operator !( const T& pred ) {
 return std::not1( pred );
}

The compiler complained:

Error 5 error C2955: 'std::unary_function' : use of class template requires template argument list c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 223 1 Graphic
Error 7 error C2451: conditional expression of type 'std::unary_negate<_Fn1>' is illegal c:\program files\microsoft visual studio 10.0\vc\include\ostream 529 1 Graphic
Error 3 error C2146: syntax error : missing ',' before identifier 'argument_type' c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 222 1 Graphic
Error 4 error C2065: 'argument_type' : undeclared identifier c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 222 1 Graphic
Error 2 error C2039: 'argument_type' : is not a member of 'std::basic_ostream<_Elem,_Traits>::sentry' c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 222 1 Graphic
Error 6 error C2039: 'argument_type' : is not a member of 'std::basic_ostream<_Elem,_Traits>::sentry' c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 230 1 Graphic

Any idea?

Update
Follow "templatetypedef" solution, I got new error:

Error 3 error C2831: 'operator !' cannot have default parameters c:\visual studio 2010 projects\graphic\graphic\main.cpp 39 1 Graphic
Error 2 error C2808: unary 'operator !' has too many formal parameters c:\visual studio 2010 projects\graphic\graphic\main.cpp 39 1 Graphic
Error 4 error C2675: unary '!' : 'is_prime' does not define this operator or a conversion to a type acceptable to the predefined operator c:\visual studio 2010 projects\graphic\graphic\main.cpp 52 1 Graphic

Update 1
Complete code:

#include <iostream>
#include <functional>
#include <utility>
#include <cmath>
#include <algorithm>
#include <iterator>
#include <string>

#include <boost/assign.hpp>
#include <boost/assign/std/vector.hpp>
#include <boost/assign/std/map.hpp>
#include <boost/assign/std/set.hpp>
#include <boost/assign/std/list.hpp>
#include <boost/assign/std/stack.hpp>
#include <boost/assign/std/deque.hpp>

struct is_prime : std::unary_function<int, bool> {
 bool operator()( int n ) const {
  if( n < 2 )
   return 0;
  if( n == 2 || n == 3 )
   return 1;
  if( n % 2 == 0 || n % 3 == 0 )
   return 0;
  int upper_bound = std::sqrt( static_cast<double>( n ) );
  for( int pf = 5, step = 2; pf <= upper_bound; ) {
   if( n % pf == 0 )
    return 0;
   pf  += step;
   step = 6 - step;
  }
  return 1;
 }
};

/*
template<typename T>
inline std::unary_negate<T> operator !( const T& pred, typename T::argument_type* dummy = 0 ) {
 return std::not1<T>( pred );
}
*/

inline std::unary_negate<is_prime> operator !( const is_prime& pred ) {
 return std::not1( pred );
}

template<typename T>
inline void print_con( const T& con, const std::string& ms = "", const std::string& sep = ", " ) {
 std::cout << ms << '\n';
 std::copy( con.begin(), con.end(), std::ostream_iterator<typename T::value_type>( std::cout, sep.c_str() ) );
 std::cout << "\n\n";
}

int main() {
 using namespace boost::assign;
 std::vector<int> nums;
 nums += 1, 3, 5, 7, 9;
 nums.erase( remove_if( nums.begin(), nums.end(), !is_prime() ), nums.end() );
 print_con( nums, "After remove all primes" ); 
}

Thanks,
Chan Nguyen

Upvotes: 3

Views: 1633

Answers (3)

templatetypedef
templatetypedef

Reputation: 372814

Here’s one way to do this based on the "Substitution Failure is Not an Error" (SFINAE) principle. This states that during overload resolution, if the C++ compiler tries instantiating a template and encounters a problem, it doesn't trigger a compilation error. Instead, it just removes that particular template from consideration. The idea here is that if you overload a function (i.e. have lots of different functions with the same name but different parameters), some of which are templates and some of which aren't, you'll never get a compiler error if one of the candidate template functions makes no sense.

This particular technique can help you out here it two different ways. First, let's suppose right now that I have a black box called "IsAdaptable" that can look at a type and tell me whether or not a particular type is an adaptable function. Given this information, how can we make your operator ! function only apply to types that are adaptable? Well, using the SFINAE principle, we'd need to somehow make the template function signature invalid in the case where the input type isn't adaptable. There are a lot of ways to do this, but one common one uses a helper template called "enable if." Here's the full implementation of an enable-if template:

template <bool Condition, typename T> struct EnableIf {
     typedef T type;
};
template <typename T> struct EnableIf<false, T> {
     // Empty
};

This is a strange template. It's parameterized over two arguments - a boolean condition and a type - in such a way that if the condition is true, EnableIf exports a its second argument as type, and if the condition is false, it doesn't export anything at all.

The utility of the enable if template is that it lets you write functions that are only available if certain properties hold true of the template argument. So suppose, for example, that you want to write a function like this one:

template <typename T>
ReturnType MyFunction(/* ... arguments ... */) {
    /* ... body ... */
}

However, you only want this function to be usable if some predicate "Predicate" holds for the type T. Then you can change the function to look like this:

template <typename T>
typename EnableIf<Predicate<T>::value, ActualReturnType>::type MyFunction(/* ... arguments ... */) {
    /* ... body here ... */
}

The idea here is that if you call the function MyFunction with some type parameter T, one of two cases holds. First, if Predicate<T>::value is true, then the EnableIf ends up exporting a nested type called type that's equivalent to ActualReturnType, and the function works as usual. On the other hand, though, if Predicate<T>::value is false, then the EnableIf instance doesn't have a type type nested inside of it. The compiler detects this, and since "substitution failure is not an error," it removes the function from consideration. If there are other possible overloads still left in consideration, then the compiler will pick some other one of those. If not, then it reports an error, since none of the possible MyFunction functions are still valid.

The idea, in your case, is to write operator ! like this:

template <typename Pred>
typename EnableIf<IsAdaptable<Pred>::value, std::unary_negate<Pred> >::type
operator! (const Pred& p) {
     return std::not1(p);
}

This uses the above trick to say "this operator ! function is available only for types that are adaptable functions." We're now halfway there - given an implementation of IsAdaptable, we're done.

The problem is that writing IsAdaptable is not at all easy. It ends up using a series of hacks so awful that it will make you cry. But fear not! It's not that hard to understand once you see the big picture.

The way that we can make IsAdaptable work is by using SFINAE in an entirely new way. Here's a high-level sketch of the idea. Suppose that we have two functions that are overloads of another, one of which returns a type called "Yes" and one of which returns a type called "No." We then write these functions so that the "Yes" version always takes precedence over the "No" version, but the "Yes" version is only available if some given type is adaptable. In that case, upon calling the function, one of two cases will hold true:

  1. The type in question is adaptable. In that case, both versions of the function are available, but the "Yes" version has priority over the "No" version and so the "Yes" version is called.
  2. The type in question is not adaptable. In that case, the only version of the function that's available is the "No" version, and so that's the one that's called.

But how can you construct functions like this? It turns out there's a clever but not particularly complex solution to this:

template <typename T> Yes TestFunction(typename T::argument_type* argument);
template <typename T> No  TestFunction(...);

Both of these functions are named TestFunction. The first one is parameterized over some type T and takes as an argument a T::argument_type* pointer. The second takes a varargs parameter. Now, if for some type T we try making the following function call:

TestFunction<T>(NULL);

Then the first version of this function is only available if T has a type called argument_type nested inside of it. The second version is always available. Due to the way that C++ overload resolution works, though, a varargs function (a function taking ...) will never be selected over some function that's more specific with its argument list. Consequently, this above expression has type Yes if T has argument_type nested inside it and has type No otherwise. We're almost there - if we can somehow detect what the return type is, we've got a test to see whether T is adaptable!

The way that we'll do this final step is a bit roundabout. The idea is that we'll define types Yes and No so that they have different sizes:

typedef char Yes;
struct No {
    char dummy[32];
};

Now we know that sizeof(Yes) == 1 and sizeof(No) > 1. Pulling all this together gives us the final version of IsAdaptable:

template <typename T> struct IsAdaptable {
private:
    typedef char Yes;
    struct No {
         char dummy[32];
    };

    template <typename U> static Yes test(typename U::argument_type*);
    template <typename U> static No  test(...);

public:
    static const bool value = (sizeof(test<T>(0)) == sizeof(Yes));
};

This struct contains all of the above functions, and then exports 'true' if test<T>(0) returns a Yes and false otherwise. Notice that because sizeof doesn't actually evaluate its argument (it just tells you how many bytes it uses) we never actually have to implement either of these functions.

Pulling absolutely everything together gives this final solution:

template <bool cond, typename T> struct EnableIf {
  typedef T type;
};
template <typename T> struct EnableIf<false, T> {

};

template <typename T> struct IsAdaptable {
private:
  typedef char Yes;
  struct No {
    char buffer[32];
  };

  template <typename U> static Yes test(typename U::argument_type*);
  template <typename U> static No  test(...);

public:
  static const bool result = (sizeof(test<T>(0)) == sizeof(Yes));
};

template<typename T>                                                                                                                                           
inline typename EnableIf<IsAdaptable<T>::result, std::unary_negate<T> >::type operator !(const T& pred) {                                                      
  return std::not1( pred );                                                                                                                                    
}  

Hope this helps!

Upvotes: 3

wilhelmtell
wilhelmtell

Reputation: 58677

The obscure error you get is a classic example of the C++ template errors. What it's trying to say is that you're trying to call the operator !() function with an incompatible type. std::unary_negate<> expects its template parameter to have an argument_type typedef, and the object you passed to the function doesn't have this typedef.

That said, I'm not sure what you're trying to achieve. The standard library already has this functionality, albeit with a different name: std::not1(). Why do you want to wrap it? Users know exactly what std::not1(f) means, but !f looks to me like a pitfall with respect to what a user might expect in a given context.

Upvotes: 2

Vicente Botet Escriba
Vicente Botet Escriba

Reputation: 4345

I don't know exactly what you are really compiling, but I suspect that you are using std::unary_function without the needed template parameters, isn't it?

Could you show the complete code?

Upvotes: 0

Related Questions